qwen-sci 0.2.2__py3-none-any.whl
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.
- qwen_sci-0.2.2.dist-info/METADATA +492 -0
- qwen_sci-0.2.2.dist-info/RECORD +549 -0
- qwen_sci-0.2.2.dist-info/WHEEL +4 -0
- qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
- qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
- src/1.txt +1 -0
- src/__init__.py +1 -0
- src/__main__.py +5 -0
- src/agents/__init__.py +1 -0
- src/agents/experiment_design_agent/__init__.py +268 -0
- src/agents/experiment_design_agent/artifacts.py +747 -0
- src/agents/experiment_design_agent/cache.py +319 -0
- src/agents/experiment_design_agent/completeness.py +130 -0
- src/agents/experiment_design_agent/contracts.py +771 -0
- src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
- src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
- src/agents/experiment_design_agent/discipline_catalog.py +178 -0
- src/agents/experiment_design_agent/evidence_cards.py +338 -0
- src/agents/experiment_design_agent/evidence_planner.py +452 -0
- src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
- src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
- src/agents/experiment_design_agent/idea_adapter.py +100 -0
- src/agents/experiment_design_agent/idea_intake.py +87 -0
- src/agents/experiment_design_agent/llm_json.py +328 -0
- src/agents/experiment_design_agent/orchestrator.py +1220 -0
- src/agents/experiment_design_agent/reasoning_context.py +183 -0
- src/agents/experiment_design_agent/reasoning_validation.py +546 -0
- src/agents/experiment_design_agent/run.py +481 -0
- src/agents/experiment_design_agent/run_logging.py +292 -0
- src/agents/experiment_design_agent/scope_gate.py +198 -0
- src/agents/experiment_design_agent/study_type_composer.py +1360 -0
- src/agents/experiment_design_agent/survey_evidence.py +1381 -0
- src/agents/experiment_design_agent/template_router.py +195 -0
- src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
- src/agents/idea_agent/__init__.py +1 -0
- src/agents/idea_agent/agent/__init__.py +6 -0
- src/agents/idea_agent/agent/artifacts.py +308 -0
- src/agents/idea_agent/agent/base.py +122 -0
- src/agents/idea_agent/agent/ligagent.py +488 -0
- src/agents/idea_agent/agent/mcts.py +2060 -0
- src/agents/idea_agent/agent/prompts/__init__.py +48 -0
- src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
- src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
- src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
- src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
- src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
- src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
- src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
- src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
- src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
- src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
- src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
- src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
- src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
- src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
- src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
- src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
- src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
- src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
- src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
- src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
- src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
- src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
- src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
- src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
- src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
- src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
- src/agents/idea_agent/example/idea_result.json +900 -0
- src/agents/idea_agent/run.py +276 -0
- src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
- src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
- src/agents/idea_agent/tests/test_idea_debate.py +480 -0
- src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
- src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
- src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
- src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
- src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
- src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
- src/agents/idea_agent/tests/test_result_persistence.py +183 -0
- src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
- src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
- src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
- src/agents/idea_agent/utils/core/__init__.py +1 -0
- src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
- src/agents/idea_agent/utils/core/chat_errors.py +95 -0
- src/agents/idea_agent/utils/core/chat_router.py +130 -0
- src/agents/idea_agent/utils/core/chat_transport.py +188 -0
- src/agents/idea_agent/utils/core/config_loader.py +166 -0
- src/agents/idea_agent/utils/core/json_utils.py +28 -0
- src/agents/idea_agent/utils/core/logger.py +287 -0
- src/agents/idea_agent/utils/core/progress.py +58 -0
- src/agents/idea_agent/utils/core/response_parsing.py +57 -0
- src/agents/idea_agent/utils/core/run_inputs.py +198 -0
- src/agents/idea_agent/utils/mcts/__init__.py +1 -0
- src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
- src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
- src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
- src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
- src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
- src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
- src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
- src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
- src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
- src/agents/idea_agent/utils/papers/__init__.py +1 -0
- src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
- src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
- src/agents/idea_agent/utils/prompting/__init__.py +1 -0
- src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
- src/agents/idea_agent/utils/workflow/__init__.py +1 -0
- src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
- src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
- src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
- src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
- src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
- src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
- src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
- src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
- src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
- src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
- src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
- src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
- src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
- src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
- src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
- src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
- src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
- src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
- src/agents/research_plan_author/__init__.py +50 -0
- src/agents/research_plan_author/artifacts.py +270 -0
- src/agents/research_plan_author/authoring_blueprint.py +1246 -0
- src/agents/research_plan_author/bibtex_renderer.py +252 -0
- src/agents/research_plan_author/contract_repair.py +131 -0
- src/agents/research_plan_author/contracts.py +745 -0
- src/agents/research_plan_author/cross_section_editor.py +232 -0
- src/agents/research_plan_author/document_quality.py +987 -0
- src/agents/research_plan_author/idea_evolution.py +223 -0
- src/agents/research_plan_author/input_loader.py +76 -0
- src/agents/research_plan_author/latex_compiler.py +260 -0
- src/agents/research_plan_author/latex_safety.py +159 -0
- src/agents/research_plan_author/llm_json.py +74 -0
- src/agents/research_plan_author/markdown_renderer.py +275 -0
- src/agents/research_plan_author/pdf_validator.py +149 -0
- src/agents/research_plan_author/render.py +166 -0
- src/agents/research_plan_author/render_artifacts.py +187 -0
- src/agents/research_plan_author/run.py +765 -0
- src/agents/research_plan_author/run_logging.py +209 -0
- src/agents/research_plan_author/section_cache.py +184 -0
- src/agents/research_plan_author/section_composer.py +1764 -0
- src/agents/research_plan_author/section_router.py +209 -0
- src/agents/research_plan_author/semantic_validator.py +254 -0
- src/agents/research_plan_author/source_bundle.py +46 -0
- src/agents/research_plan_author/source_registry.py +208 -0
- src/agents/research_plan_author/survey_source_loader.py +137 -0
- src/agents/research_plan_author/template_adapter.py +173 -0
- src/agents/research_plan_author/template_profile.py +204 -0
- src/agents/research_plan_author/tex_renderer.py +496 -0
- src/agents/research_plan_author/theory_presentation.py +134 -0
- src/agents/research_plan_author/theory_spine.py +703 -0
- src/agents/survey_agent/Dockerfile +54 -0
- src/agents/survey_agent/README.md +350 -0
- src/agents/survey_agent/config/deep_survey.yaml +217 -0
- src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
- src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
- src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
- src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
- src/agents/survey_agent/env_report.md +348 -0
- src/agents/survey_agent/modules/__init__.py +0 -0
- src/agents/survey_agent/modules/code_collector.py +2194 -0
- src/agents/survey_agent/modules/code_report_generator.py +1282 -0
- src/agents/survey_agent/modules/data_manager.py +1713 -0
- src/agents/survey_agent/modules/database.py +167 -0
- src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
- src/agents/survey_agent/modules/fulltext_http.py +134 -0
- src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
- src/agents/survey_agent/modules/judge.py +434 -0
- src/agents/survey_agent/modules/outcome_RAG.py +236 -0
- src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
- src/agents/survey_agent/modules/pe.py +1729 -0
- src/agents/survey_agent/modules/pseudo_creator.py +782 -0
- src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
- src/agents/survey_agent/modules/refine_agent.py +1952 -0
- src/agents/survey_agent/modules/survey_generator.py +5691 -0
- src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
- src/agents/survey_agent/modules/visual_prompts.py +327 -0
- src/agents/survey_agent/modules/work_analyzer.py +2750 -0
- src/agents/survey_agent/modules/work_collector.py +5649 -0
- src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
- src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
- src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
- src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
- src/agents/survey_agent/utils/__init__.py +0 -0
- src/agents/survey_agent/utils/ablation_utils.py +317 -0
- src/agents/survey_agent/utils/api_call.py +2518 -0
- src/agents/survey_agent/utils/chat_utils.py +82 -0
- src/agents/survey_agent/utils/config_utils.py +70 -0
- src/agents/survey_agent/utils/convert_to_md.py +29 -0
- src/agents/survey_agent/utils/err_info.py +24 -0
- src/agents/survey_agent/utils/file_utils.py +176 -0
- src/agents/survey_agent/utils/gpu_utils.py +145 -0
- src/agents/survey_agent/utils/graph_visualizer.py +279 -0
- src/agents/survey_agent/utils/html_utils.py +181 -0
- src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
- src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
- src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
- src/agents/survey_agent/utils/mineru_utils.py +396 -0
- src/agents/survey_agent/utils/paper.py +144 -0
- src/agents/survey_agent/utils/read_yaml.py +15 -0
- src/agents/survey_agent/utils/repo_utils.py +167 -0
- src/agents/survey_agent/utils/rich_logger.py +77 -0
- src/agents/survey_agent/utils/step2v2.py +874 -0
- src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
- src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
- src/agents/survey_agent/utils/utils.py +118 -0
- src/agents/survey_agent/utils/work.py +2 -0
- src/cli.py +1611 -0
- src/config/__init__.py +213 -0
- src/config/default.yaml +1122 -0
- src/harness/LICENSE +21 -0
- src/harness/VENDORED.md +14 -0
- src/harness/src/__init__.py +0 -0
- src/harness/src/openharness/__init__.py +0 -0
- src/harness/src/openharness/__main__.py +6 -0
- src/harness/src/openharness/api/__init__.py +21 -0
- src/harness/src/openharness/api/client.py +281 -0
- src/harness/src/openharness/api/codex_client.py +407 -0
- src/harness/src/openharness/api/copilot_auth.py +240 -0
- src/harness/src/openharness/api/copilot_client.py +134 -0
- src/harness/src/openharness/api/errors.py +19 -0
- src/harness/src/openharness/api/openai_client.py +577 -0
- src/harness/src/openharness/api/provider.py +186 -0
- src/harness/src/openharness/api/registry.py +437 -0
- src/harness/src/openharness/api/usage.py +17 -0
- src/harness/src/openharness/auth/__init__.py +29 -0
- src/harness/src/openharness/auth/external.py +610 -0
- src/harness/src/openharness/auth/flows.py +189 -0
- src/harness/src/openharness/auth/manager.py +482 -0
- src/harness/src/openharness/auth/storage.py +269 -0
- src/harness/src/openharness/autopilot/__init__.py +23 -0
- src/harness/src/openharness/autopilot/service.py +2239 -0
- src/harness/src/openharness/autopilot/types.py +91 -0
- src/harness/src/openharness/bridge/__init__.py +20 -0
- src/harness/src/openharness/bridge/manager.py +106 -0
- src/harness/src/openharness/bridge/session_runner.py +46 -0
- src/harness/src/openharness/bridge/types.py +37 -0
- src/harness/src/openharness/bridge/work_secret.py +41 -0
- src/harness/src/openharness/channels/UPSTREAM +6 -0
- src/harness/src/openharness/channels/__init__.py +22 -0
- src/harness/src/openharness/channels/adapter.py +131 -0
- src/harness/src/openharness/channels/bus/__init__.py +6 -0
- src/harness/src/openharness/channels/bus/events.py +38 -0
- src/harness/src/openharness/channels/bus/queue.py +44 -0
- src/harness/src/openharness/channels/impl/__init__.py +6 -0
- src/harness/src/openharness/channels/impl/base.py +141 -0
- src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
- src/harness/src/openharness/channels/impl/discord.py +311 -0
- src/harness/src/openharness/channels/impl/email.py +410 -0
- src/harness/src/openharness/channels/impl/feishu.py +1342 -0
- src/harness/src/openharness/channels/impl/manager.py +257 -0
- src/harness/src/openharness/channels/impl/matrix.py +700 -0
- src/harness/src/openharness/channels/impl/mochat.py +897 -0
- src/harness/src/openharness/channels/impl/qq.py +141 -0
- src/harness/src/openharness/channels/impl/slack.py +284 -0
- src/harness/src/openharness/channels/impl/telegram.py +525 -0
- src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
- src/harness/src/openharness/cli.py +2551 -0
- src/harness/src/openharness/commands/__init__.py +21 -0
- src/harness/src/openharness/commands/registry.py +2783 -0
- src/harness/src/openharness/config/__init__.py +34 -0
- src/harness/src/openharness/config/paths.py +160 -0
- src/harness/src/openharness/config/schema.py +119 -0
- src/harness/src/openharness/config/settings.py +1104 -0
- src/harness/src/openharness/coordinator/__init__.py +12 -0
- src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
- src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
- src/harness/src/openharness/engine/__init__.py +80 -0
- src/harness/src/openharness/engine/cost_tracker.py +24 -0
- src/harness/src/openharness/engine/messages.py +222 -0
- src/harness/src/openharness/engine/query.py +1188 -0
- src/harness/src/openharness/engine/query_engine.py +313 -0
- src/harness/src/openharness/engine/stream_events.py +90 -0
- src/harness/src/openharness/hooks/__init__.py +50 -0
- src/harness/src/openharness/hooks/events.py +20 -0
- src/harness/src/openharness/hooks/executor.py +242 -0
- src/harness/src/openharness/hooks/hot_reload.py +31 -0
- src/harness/src/openharness/hooks/loader.py +68 -0
- src/harness/src/openharness/hooks/schemas.py +66 -0
- src/harness/src/openharness/hooks/types.py +38 -0
- src/harness/src/openharness/keybindings/__init__.py +14 -0
- src/harness/src/openharness/keybindings/default_bindings.py +11 -0
- src/harness/src/openharness/keybindings/loader.py +22 -0
- src/harness/src/openharness/keybindings/parser.py +18 -0
- src/harness/src/openharness/keybindings/resolver.py +13 -0
- src/harness/src/openharness/mcp/__init__.py +79 -0
- src/harness/src/openharness/mcp/client.py +298 -0
- src/harness/src/openharness/mcp/config.py +16 -0
- src/harness/src/openharness/mcp/types.py +76 -0
- src/harness/src/openharness/memory/__init__.py +25 -0
- src/harness/src/openharness/memory/agent.py +91 -0
- src/harness/src/openharness/memory/manager.py +183 -0
- src/harness/src/openharness/memory/memdir.py +52 -0
- src/harness/src/openharness/memory/migrate.py +137 -0
- src/harness/src/openharness/memory/paths.py +22 -0
- src/harness/src/openharness/memory/relevance.py +145 -0
- src/harness/src/openharness/memory/scan.py +116 -0
- src/harness/src/openharness/memory/schema.py +443 -0
- src/harness/src/openharness/memory/search.py +71 -0
- src/harness/src/openharness/memory/team.py +90 -0
- src/harness/src/openharness/memory/types.py +33 -0
- src/harness/src/openharness/memory/usage.py +153 -0
- src/harness/src/openharness/output_styles/__init__.py +5 -0
- src/harness/src/openharness/output_styles/loader.py +42 -0
- src/harness/src/openharness/permissions/__init__.py +26 -0
- src/harness/src/openharness/permissions/checker.py +200 -0
- src/harness/src/openharness/permissions/modes.py +13 -0
- src/harness/src/openharness/personalization/__init__.py +6 -0
- src/harness/src/openharness/personalization/extractor.py +138 -0
- src/harness/src/openharness/personalization/rules.py +64 -0
- src/harness/src/openharness/personalization/session_hook.py +65 -0
- src/harness/src/openharness/platforms.py +86 -0
- src/harness/src/openharness/plugins/__init__.py +53 -0
- src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
- src/harness/src/openharness/plugins/installer.py +39 -0
- src/harness/src/openharness/plugins/loader.py +730 -0
- src/harness/src/openharness/plugins/schemas.py +24 -0
- src/harness/src/openharness/plugins/types.py +59 -0
- src/harness/src/openharness/prompts/__init__.py +14 -0
- src/harness/src/openharness/prompts/claudemd.py +48 -0
- src/harness/src/openharness/prompts/context.py +187 -0
- src/harness/src/openharness/prompts/environment.py +135 -0
- src/harness/src/openharness/prompts/system_prompt.py +109 -0
- src/harness/src/openharness/sandbox/Dockerfile +6 -0
- src/harness/src/openharness/sandbox/__init__.py +33 -0
- src/harness/src/openharness/sandbox/adapter.py +148 -0
- src/harness/src/openharness/sandbox/docker_backend.py +232 -0
- src/harness/src/openharness/sandbox/docker_image.py +103 -0
- src/harness/src/openharness/sandbox/path_validator.py +37 -0
- src/harness/src/openharness/sandbox/session.py +63 -0
- src/harness/src/openharness/services/__init__.py +30 -0
- src/harness/src/openharness/services/autodream/__init__.py +34 -0
- src/harness/src/openharness/services/autodream/backup.py +104 -0
- src/harness/src/openharness/services/autodream/lock.py +138 -0
- src/harness/src/openharness/services/autodream/prompt.py +128 -0
- src/harness/src/openharness/services/autodream/service.py +313 -0
- src/harness/src/openharness/services/compact/__init__.py +1725 -0
- src/harness/src/openharness/services/cron.py +137 -0
- src/harness/src/openharness/services/cron_scheduler.py +595 -0
- src/harness/src/openharness/services/lsp/__init__.py +216 -0
- src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
- src/harness/src/openharness/services/oauth/__init__.py +0 -0
- src/harness/src/openharness/services/session_backend.py +97 -0
- src/harness/src/openharness/services/session_memory/__init__.py +139 -0
- src/harness/src/openharness/services/session_storage.py +230 -0
- src/harness/src/openharness/services/token_estimation.py +15 -0
- src/harness/src/openharness/services/tool_outputs.py +55 -0
- src/harness/src/openharness/skills/__init__.py +44 -0
- src/harness/src/openharness/skills/_frontmatter.py +97 -0
- src/harness/src/openharness/skills/bundled/__init__.py +75 -0
- src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
- src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
- src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
- src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
- src/harness/src/openharness/skills/bundled/content/review.md +33 -0
- src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
- src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
- src/harness/src/openharness/skills/bundled/content/test.md +38 -0
- src/harness/src/openharness/skills/loader.py +229 -0
- src/harness/src/openharness/skills/registry.py +29 -0
- src/harness/src/openharness/skills/types.py +24 -0
- src/harness/src/openharness/state/__init__.py +6 -0
- src/harness/src/openharness/state/app_state.py +30 -0
- src/harness/src/openharness/state/store.py +40 -0
- src/harness/src/openharness/swarm/__init__.py +70 -0
- src/harness/src/openharness/swarm/in_process.py +693 -0
- src/harness/src/openharness/swarm/lockfile.py +24 -0
- src/harness/src/openharness/swarm/mailbox.py +522 -0
- src/harness/src/openharness/swarm/permission_sync.py +1168 -0
- src/harness/src/openharness/swarm/registry.py +410 -0
- src/harness/src/openharness/swarm/spawn_utils.py +229 -0
- src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
- src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
- src/harness/src/openharness/swarm/types.py +398 -0
- src/harness/src/openharness/swarm/worktree.py +315 -0
- src/harness/src/openharness/tasks/__init__.py +18 -0
- src/harness/src/openharness/tasks/local_agent_task.py +28 -0
- src/harness/src/openharness/tasks/local_shell_task.py +17 -0
- src/harness/src/openharness/tasks/manager.py +475 -0
- src/harness/src/openharness/tasks/stop_task.py +11 -0
- src/harness/src/openharness/tasks/types.py +32 -0
- src/harness/src/openharness/themes/__init__.py +21 -0
- src/harness/src/openharness/themes/builtin.py +89 -0
- src/harness/src/openharness/themes/loader.py +55 -0
- src/harness/src/openharness/themes/schema.py +54 -0
- src/harness/src/openharness/tools/__init__.py +107 -0
- src/harness/src/openharness/tools/agent_tool.py +141 -0
- src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
- src/harness/src/openharness/tools/base.py +80 -0
- src/harness/src/openharness/tools/bash_tool.py +259 -0
- src/harness/src/openharness/tools/brief_tool.py +33 -0
- src/harness/src/openharness/tools/config_tool.py +54 -0
- src/harness/src/openharness/tools/cron_create_tool.py +105 -0
- src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
- src/harness/src/openharness/tools/cron_list_tool.py +69 -0
- src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
- src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
- src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
- src/harness/src/openharness/tools/file_edit_tool.py +95 -0
- src/harness/src/openharness/tools/file_read_tool.py +72 -0
- src/harness/src/openharness/tools/file_write_tool.py +87 -0
- src/harness/src/openharness/tools/glob_tool.py +175 -0
- src/harness/src/openharness/tools/grep_tool.py +374 -0
- src/harness/src/openharness/tools/image_generation_tool.py +447 -0
- src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
- src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
- src/harness/src/openharness/tools/lsp_tool.py +154 -0
- src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
- src/harness/src/openharness/tools/mcp_tool.py +72 -0
- src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
- src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
- src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
- src/harness/src/openharness/tools/send_message_tool.py +58 -0
- src/harness/src/openharness/tools/skill_tool.py +43 -0
- src/harness/src/openharness/tools/sleep_tool.py +32 -0
- src/harness/src/openharness/tools/task_create_tool.py +56 -0
- src/harness/src/openharness/tools/task_get_tool.py +33 -0
- src/harness/src/openharness/tools/task_list_tool.py +35 -0
- src/harness/src/openharness/tools/task_output_tool.py +35 -0
- src/harness/src/openharness/tools/task_stop_tool.py +30 -0
- src/harness/src/openharness/tools/task_update_tool.py +50 -0
- src/harness/src/openharness/tools/team_create_tool.py +31 -0
- src/harness/src/openharness/tools/team_delete_tool.py +30 -0
- src/harness/src/openharness/tools/todo_write_tool.py +46 -0
- src/harness/src/openharness/tools/tool_search_tool.py +38 -0
- src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
- src/harness/src/openharness/tools/web_search_tool.py +119 -0
- src/harness/src/openharness/ui/__init__.py +5 -0
- src/harness/src/openharness/ui/app.py +320 -0
- src/harness/src/openharness/ui/backend_host.py +941 -0
- src/harness/src/openharness/ui/coordinator_drain.py +198 -0
- src/harness/src/openharness/ui/input.py +32 -0
- src/harness/src/openharness/ui/output.py +265 -0
- src/harness/src/openharness/ui/permission_dialog.py +14 -0
- src/harness/src/openharness/ui/protocol.py +247 -0
- src/harness/src/openharness/ui/react_launcher.py +179 -0
- src/harness/src/openharness/ui/runtime.py +799 -0
- src/harness/src/openharness/ui/textual_app.py +495 -0
- src/harness/src/openharness/utils/__init__.py +0 -0
- src/harness/src/openharness/utils/file_lock.py +86 -0
- src/harness/src/openharness/utils/fs.py +98 -0
- src/harness/src/openharness/utils/helpers.py +77 -0
- src/harness/src/openharness/utils/network_guard.py +340 -0
- src/harness/src/openharness/utils/shell.py +147 -0
- src/harness/src/openharness/vim/__init__.py +5 -0
- src/harness/src/openharness/vim/transitions.py +8 -0
- src/harness/src/openharness/voice/__init__.py +7 -0
- src/harness/src/openharness/voice/keyterms.py +10 -0
- src/harness/src/openharness/voice/stream_stt.py +8 -0
- src/harness/src/openharness/voice/voice_mode.py +44 -0
- src/llm/__init__.py +29 -0
- src/llm/image_generation.py +402 -0
- src/llm/provider_registry.py +419 -0
- src/llm/runtime_env.py +64 -0
- src/llm/structured_output.py +131 -0
- src/llm/vision.py +198 -0
- src/memory/__init__.py +2 -0
- src/memory/api/__init__.py +1 -0
- src/memory/api/base_memory_system_api.py +98 -0
- src/memory/api/base_symbolic_memory_system_api.py +190 -0
- src/memory/api/base_vector_memory_system_api.py +116 -0
- src/memory/api/faiss_memory_system_api.py +228 -0
- src/memory/api/slot_process_api.py +827 -0
- src/memory/api/symbolic_memory_system_api.py +572 -0
- src/memory/memory_system/__init__.py +48 -0
- src/memory/memory_system/component_taxonomy.py +430 -0
- src/memory/memory_system/llm.py +437 -0
- src/memory/memory_system/models.py +168 -0
- src/memory/memory_system/user_prompt.py +418 -0
- src/memory/memory_system/utils.py +282 -0
- src/memory/memory_system/vectorstore.py +311 -0
- src/memory/memory_system/working_slot.py +99 -0
- src/pipeline/__init__.py +129 -0
- src/pipeline/contracts.py +531 -0
- src/pipeline/discipline_taxonomy.py +916 -0
- src/pipeline/evidence_coverage_ledger.py +1150 -0
- src/pipeline/evidence_refinement.py +620 -0
- src/pipeline/experiment_to_symbolic.py +137 -0
- src/pipeline/multimodal_evidence/__init__.py +28 -0
- src/pipeline/multimodal_evidence/capabilities.py +107 -0
- src/pipeline/multimodal_evidence/claims.py +135 -0
- src/pipeline/multimodal_evidence/contract.py +291 -0
- src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
- src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
- src/pipeline/multimodal_evidence/manifest.py +254 -0
- src/pipeline/multimodal_evidence/native_analysis.py +479 -0
- src/pipeline/multimodal_evidence/perception.py +277 -0
- src/pipeline/multimodal_evidence/query_binding.py +65 -0
- src/pipeline/multimodal_evidence/rendering.py +120 -0
- src/pipeline/multimodal_evidence/safety.py +42 -0
- src/pipeline/multimodal_evidence/sampling.py +69 -0
- src/pipeline/multimodal_evidence/service.py +148 -0
- src/pipeline/multimodal_evidence/survey_integration.py +460 -0
- src/pipeline/paper_identity.py +52 -0
- src/pipeline/research_design_inventory.py +338 -0
- src/pipeline/research_domain_resolution.py +669 -0
- src/pipeline/research_identity.py +548 -0
- src/pipeline/research_question_contract.py +646 -0
- src/pipeline/retrieval_lanes.py +889 -0
- src/pipeline/run_loop.py +862 -0
- src/pipeline/science_events.py +30 -0
- src/pipeline/science_manifests.py +602 -0
- src/pipeline/science_run.py +918 -0
- src/pipeline/science_stages.py +709 -0
- src/pipeline/science_workflow.py +807 -0
- src/pipeline/sh_cluster_projection.py +306 -0
- src/pipeline/sh_graph_provenance.py +648 -0
- src/pipeline/subhypothesis_decomposition.py +392 -0
- src/pipeline/subhypothesis_evidence.py +856 -0
- src/pipeline/survey_evidence_plan.py +858 -0
- src/pipeline/survey_gap_adjudication.py +647 -0
- src/pipeline/survey_gap_candidates.py +840 -0
- src/pipeline/survey_gap_ledger.py +683 -0
- src/pipeline/survey_gap_triage.py +401 -0
- src/pipeline/survey_handoff_persistence.py +481 -0
- src/pipeline/survey_handoff_projection.py +457 -0
- src/pipeline/survey_idea_handoff.py +1112 -0
- src/pipeline/survey_idea_loader.py +289 -0
- src/research_run_ids.py +11 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""File-backed session memory for compact continuity."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from hashlib import sha1
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from openharness.config.paths import get_data_dir
|
|
9
|
+
from openharness.engine.messages import ConversationMessage, ToolResultBlock
|
|
10
|
+
from openharness.services.token_estimation import estimate_tokens
|
|
11
|
+
from openharness.utils.fs import atomic_write_text
|
|
12
|
+
|
|
13
|
+
MAX_SESSION_MEMORY_CHARS = 12_000
|
|
14
|
+
MAX_RECENT_LINES = 80
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_session_memory_dir(cwd: str | Path) -> Path:
|
|
18
|
+
"""Return the project session-memory directory."""
|
|
19
|
+
|
|
20
|
+
root = Path(cwd).resolve()
|
|
21
|
+
digest = sha1(str(root).encode("utf-8")).hexdigest()[:12]
|
|
22
|
+
path = get_data_dir() / "session-memory" / f"{root.name}-{digest}"
|
|
23
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
return path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_session_memory_path(cwd: str | Path, session_id: str | None = None) -> Path:
|
|
28
|
+
"""Return the markdown session-memory path."""
|
|
29
|
+
|
|
30
|
+
safe_session = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in (session_id or "default"))
|
|
31
|
+
return get_session_memory_dir(cwd) / f"{safe_session or 'default'}.md"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def prepare_session_memory_metadata(
|
|
35
|
+
cwd: str | Path,
|
|
36
|
+
tool_metadata: dict[str, object],
|
|
37
|
+
*,
|
|
38
|
+
session_id: str | None = None,
|
|
39
|
+
) -> Path:
|
|
40
|
+
"""Ensure metadata points compaction to the session-memory file."""
|
|
41
|
+
|
|
42
|
+
sid = session_id or str(tool_metadata.get("session_id") or "default")
|
|
43
|
+
path = get_session_memory_path(cwd, sid)
|
|
44
|
+
tool_metadata["session_memory_path"] = str(path)
|
|
45
|
+
return path
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_session_memory_content(path: str | Path | None) -> str:
|
|
49
|
+
"""Read session memory content if available."""
|
|
50
|
+
|
|
51
|
+
if not path:
|
|
52
|
+
return ""
|
|
53
|
+
candidate = Path(path).expanduser()
|
|
54
|
+
if not candidate.exists():
|
|
55
|
+
return ""
|
|
56
|
+
try:
|
|
57
|
+
return candidate.read_text(encoding="utf-8", errors="replace")
|
|
58
|
+
except OSError:
|
|
59
|
+
return ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def update_session_memory_file(
|
|
63
|
+
cwd: str | Path,
|
|
64
|
+
messages: list[ConversationMessage],
|
|
65
|
+
*,
|
|
66
|
+
tool_metadata: dict[str, object] | None = None,
|
|
67
|
+
session_id: str | None = None,
|
|
68
|
+
) -> Path:
|
|
69
|
+
"""Update the deterministic session-memory checkpoint."""
|
|
70
|
+
|
|
71
|
+
path = prepare_session_memory_metadata(cwd, tool_metadata or {}, session_id=session_id)
|
|
72
|
+
body = build_session_memory_document(messages, tool_metadata=tool_metadata)
|
|
73
|
+
atomic_write_text(path, body)
|
|
74
|
+
return path
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def build_session_memory_document(
|
|
78
|
+
messages: list[ConversationMessage],
|
|
79
|
+
*,
|
|
80
|
+
tool_metadata: dict[str, object] | None = None,
|
|
81
|
+
) -> str:
|
|
82
|
+
"""Build a compact markdown checkpoint for the current session."""
|
|
83
|
+
|
|
84
|
+
state = tool_metadata.get("task_focus_state") if isinstance(tool_metadata, dict) else None
|
|
85
|
+
goal = ""
|
|
86
|
+
next_step = ""
|
|
87
|
+
verified: list[str] = []
|
|
88
|
+
artifacts: list[str] = []
|
|
89
|
+
if isinstance(state, dict):
|
|
90
|
+
goal = str(state.get("goal") or "").strip()
|
|
91
|
+
next_step = str(state.get("next_step") or "").strip()
|
|
92
|
+
verified = [str(item).strip() for item in state.get("verified_state", []) if str(item).strip()]
|
|
93
|
+
artifacts = [str(item).strip() for item in state.get("active_artifacts", []) if str(item).strip()]
|
|
94
|
+
|
|
95
|
+
lines = ["# Session Memory", ""]
|
|
96
|
+
lines.extend(["## Current State", goal or "(no current goal recorded)", ""])
|
|
97
|
+
if next_step:
|
|
98
|
+
lines.extend(["## Next Step", next_step, ""])
|
|
99
|
+
if verified:
|
|
100
|
+
lines.extend(["## Verified Work", *[f"- {item}" for item in verified[-10:]], ""])
|
|
101
|
+
if artifacts:
|
|
102
|
+
lines.extend(["## Active Artifacts", *[f"- {item}" for item in artifacts[-10:]], ""])
|
|
103
|
+
lines.extend(["## Recent Conversation", *_recent_message_lines(messages), ""])
|
|
104
|
+
text = "\n".join(lines).strip() + "\n"
|
|
105
|
+
if len(text) > MAX_SESSION_MEMORY_CHARS:
|
|
106
|
+
text = text[:MAX_SESSION_MEMORY_CHARS].rsplit("\n", 1)[0]
|
|
107
|
+
text += "\n\n> Session memory was truncated to stay within budget.\n"
|
|
108
|
+
return text
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def session_memory_to_compact_text(content: str) -> str:
|
|
112
|
+
"""Prepare persisted session memory for insertion across compact boundaries."""
|
|
113
|
+
|
|
114
|
+
stripped = content.strip()
|
|
115
|
+
if not stripped:
|
|
116
|
+
return ""
|
|
117
|
+
if estimate_tokens(stripped) > 4_000:
|
|
118
|
+
stripped = stripped[:MAX_SESSION_MEMORY_CHARS].rsplit("\n", 1)[0]
|
|
119
|
+
return "Session memory checkpoint from earlier in this conversation:\n" + stripped
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _recent_message_lines(messages: list[ConversationMessage]) -> list[str]:
|
|
123
|
+
lines: list[str] = []
|
|
124
|
+
for message in messages[-MAX_RECENT_LINES:]:
|
|
125
|
+
line = _summarize_message(message)
|
|
126
|
+
if line:
|
|
127
|
+
lines.append(f"- {line}")
|
|
128
|
+
return lines or ["- (no recent messages)"]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _summarize_message(message: ConversationMessage) -> str:
|
|
132
|
+
text = " ".join(message.text.split())
|
|
133
|
+
if text:
|
|
134
|
+
return f"{message.role}: {text[:220]}"
|
|
135
|
+
if message.tool_uses:
|
|
136
|
+
return f"{message.role}: tool calls -> {', '.join(block.name for block in message.tool_uses[:6])}"
|
|
137
|
+
if any(isinstance(block, ToolResultBlock) for block in message.content):
|
|
138
|
+
return f"{message.role}: tool results returned"
|
|
139
|
+
return f"{message.role}: [non-text content]"
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Session persistence helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from hashlib import sha1
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
from openharness.api.usage import UsageSnapshot
|
|
13
|
+
from openharness.config.paths import get_sessions_dir
|
|
14
|
+
from openharness.engine.messages import ConversationMessage, sanitize_conversation_messages
|
|
15
|
+
from openharness.utils.fs import atomic_write_text
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_PERSISTED_TOOL_METADATA_KEYS = (
|
|
19
|
+
"permission_mode",
|
|
20
|
+
"read_file_state",
|
|
21
|
+
"invoked_skills",
|
|
22
|
+
"async_agent_state",
|
|
23
|
+
"async_agent_tasks",
|
|
24
|
+
"recent_work_log",
|
|
25
|
+
"recent_verified_work",
|
|
26
|
+
"task_focus_state",
|
|
27
|
+
"compact_checkpoints",
|
|
28
|
+
"compact_last",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _sanitize_metadata(value: Any) -> Any:
|
|
33
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
34
|
+
return value
|
|
35
|
+
if isinstance(value, Path):
|
|
36
|
+
return str(value)
|
|
37
|
+
if isinstance(value, dict):
|
|
38
|
+
return {str(key): _sanitize_metadata(item) for key, item in value.items()}
|
|
39
|
+
if isinstance(value, (list, tuple, set)):
|
|
40
|
+
return [_sanitize_metadata(item) for item in value]
|
|
41
|
+
return str(value)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _persistable_tool_metadata(tool_metadata: dict[str, object] | None) -> dict[str, Any]:
|
|
45
|
+
if not isinstance(tool_metadata, dict):
|
|
46
|
+
return {}
|
|
47
|
+
payload: dict[str, Any] = {}
|
|
48
|
+
for key in _PERSISTED_TOOL_METADATA_KEYS:
|
|
49
|
+
if key in tool_metadata:
|
|
50
|
+
payload[key] = _sanitize_metadata(tool_metadata[key])
|
|
51
|
+
return payload
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_project_session_dir(cwd: str | Path) -> Path:
|
|
55
|
+
"""Return the session directory for a project."""
|
|
56
|
+
path = Path(cwd).resolve()
|
|
57
|
+
digest = sha1(str(path).encode("utf-8")).hexdigest()[:12]
|
|
58
|
+
session_dir = get_sessions_dir() / f"{path.name}-{digest}"
|
|
59
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
return session_dir
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def save_session_snapshot(
|
|
64
|
+
*,
|
|
65
|
+
cwd: str | Path,
|
|
66
|
+
model: str,
|
|
67
|
+
system_prompt: str,
|
|
68
|
+
messages: list[ConversationMessage],
|
|
69
|
+
usage: UsageSnapshot,
|
|
70
|
+
session_id: str | None = None,
|
|
71
|
+
tool_metadata: dict[str, object] | None = None,
|
|
72
|
+
) -> Path:
|
|
73
|
+
"""Persist a session snapshot. Saves both by ID and as latest."""
|
|
74
|
+
session_dir = get_project_session_dir(cwd)
|
|
75
|
+
sid = session_id or uuid4().hex[:12]
|
|
76
|
+
now = time.time()
|
|
77
|
+
messages = sanitize_conversation_messages(messages)
|
|
78
|
+
# Extract a summary from the first user message
|
|
79
|
+
summary = ""
|
|
80
|
+
for msg in messages:
|
|
81
|
+
if msg.role == "user" and msg.text.strip():
|
|
82
|
+
summary = msg.text.strip()[:80]
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
payload = {
|
|
86
|
+
"session_id": sid,
|
|
87
|
+
"cwd": str(Path(cwd).resolve()),
|
|
88
|
+
"model": model,
|
|
89
|
+
"system_prompt": system_prompt,
|
|
90
|
+
"messages": [message.model_dump(mode="json") for message in messages],
|
|
91
|
+
"usage": usage.model_dump(),
|
|
92
|
+
"tool_metadata": _persistable_tool_metadata(tool_metadata),
|
|
93
|
+
"created_at": now,
|
|
94
|
+
"summary": summary,
|
|
95
|
+
"message_count": len(messages),
|
|
96
|
+
}
|
|
97
|
+
data = json.dumps(payload, indent=2) + "\n"
|
|
98
|
+
|
|
99
|
+
# Save as latest
|
|
100
|
+
latest_path = session_dir / "latest.json"
|
|
101
|
+
atomic_write_text(latest_path, data)
|
|
102
|
+
|
|
103
|
+
# Save by session ID
|
|
104
|
+
session_path = session_dir / f"session-{sid}.json"
|
|
105
|
+
atomic_write_text(session_path, data)
|
|
106
|
+
|
|
107
|
+
return latest_path
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _sanitize_snapshot_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|
111
|
+
"""Normalize persisted messages for forward compatibility."""
|
|
112
|
+
raw_messages = payload.get("messages", [])
|
|
113
|
+
if isinstance(raw_messages, list):
|
|
114
|
+
messages = sanitize_conversation_messages(
|
|
115
|
+
[ConversationMessage.model_validate(item) for item in raw_messages]
|
|
116
|
+
)
|
|
117
|
+
payload = dict(payload)
|
|
118
|
+
payload["messages"] = [message.model_dump(mode="json") for message in messages]
|
|
119
|
+
payload["message_count"] = len(messages)
|
|
120
|
+
return payload
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def load_session_snapshot(cwd: str | Path) -> dict[str, Any] | None:
|
|
124
|
+
"""Load the most recent session snapshot for the project."""
|
|
125
|
+
path = get_project_session_dir(cwd) / "latest.json"
|
|
126
|
+
if not path.exists():
|
|
127
|
+
return None
|
|
128
|
+
return _sanitize_snapshot_payload(json.loads(path.read_text(encoding="utf-8")))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def list_session_snapshots(cwd: str | Path, limit: int = 20) -> list[dict[str, Any]]:
|
|
132
|
+
"""List saved sessions for the project, newest first."""
|
|
133
|
+
session_dir = get_project_session_dir(cwd)
|
|
134
|
+
sessions: list[dict[str, Any]] = []
|
|
135
|
+
seen_ids: set[str] = set()
|
|
136
|
+
|
|
137
|
+
# Named session files
|
|
138
|
+
for path in sorted(session_dir.glob("session-*.json"), key=lambda p: p.stat().st_mtime, reverse=True):
|
|
139
|
+
try:
|
|
140
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
141
|
+
sid = data.get("session_id", path.stem.replace("session-", ""))
|
|
142
|
+
seen_ids.add(sid)
|
|
143
|
+
summary = data.get("summary", "")
|
|
144
|
+
if not summary:
|
|
145
|
+
# Extract from first user message
|
|
146
|
+
for msg in data.get("messages", []):
|
|
147
|
+
if msg.get("role") == "user":
|
|
148
|
+
texts = [b.get("text", "") for b in msg.get("content", []) if b.get("type") == "text"]
|
|
149
|
+
summary = " ".join(texts).strip()[:80]
|
|
150
|
+
if summary:
|
|
151
|
+
break
|
|
152
|
+
sessions.append({
|
|
153
|
+
"session_id": sid,
|
|
154
|
+
"summary": summary,
|
|
155
|
+
"message_count": data.get("message_count", len(data.get("messages", []))),
|
|
156
|
+
"model": data.get("model", ""),
|
|
157
|
+
"created_at": data.get("created_at", path.stat().st_mtime),
|
|
158
|
+
})
|
|
159
|
+
except (json.JSONDecodeError, OSError):
|
|
160
|
+
continue
|
|
161
|
+
if len(sessions) >= limit:
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
# Also include latest.json if it has no corresponding session file
|
|
165
|
+
latest_path = session_dir / "latest.json"
|
|
166
|
+
if latest_path.exists() and len(sessions) < limit:
|
|
167
|
+
try:
|
|
168
|
+
data = json.loads(latest_path.read_text(encoding="utf-8"))
|
|
169
|
+
sid = data.get("session_id", "latest")
|
|
170
|
+
if sid not in seen_ids:
|
|
171
|
+
summary = data.get("summary", "")
|
|
172
|
+
if not summary:
|
|
173
|
+
for msg in data.get("messages", []):
|
|
174
|
+
if msg.get("role") == "user":
|
|
175
|
+
texts = [b.get("text", "") for b in msg.get("content", []) if b.get("type") == "text"]
|
|
176
|
+
summary = " ".join(texts).strip()[:80]
|
|
177
|
+
if summary:
|
|
178
|
+
break
|
|
179
|
+
sessions.append({
|
|
180
|
+
"session_id": sid,
|
|
181
|
+
"summary": summary or "(latest session)",
|
|
182
|
+
"message_count": data.get("message_count", len(data.get("messages", []))),
|
|
183
|
+
"model": data.get("model", ""),
|
|
184
|
+
"created_at": data.get("created_at", latest_path.stat().st_mtime),
|
|
185
|
+
})
|
|
186
|
+
except (json.JSONDecodeError, OSError):
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
# Sort by created_at descending
|
|
190
|
+
sessions.sort(key=lambda s: s.get("created_at", 0), reverse=True)
|
|
191
|
+
return sessions[:limit]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def load_session_by_id(cwd: str | Path, session_id: str) -> dict[str, Any] | None:
|
|
195
|
+
"""Load a specific session by ID."""
|
|
196
|
+
session_dir = get_project_session_dir(cwd)
|
|
197
|
+
# Try named session first
|
|
198
|
+
path = session_dir / f"session-{session_id}.json"
|
|
199
|
+
if path.exists():
|
|
200
|
+
return _sanitize_snapshot_payload(json.loads(path.read_text(encoding="utf-8")))
|
|
201
|
+
# Fallback to latest.json if session_id matches
|
|
202
|
+
latest = session_dir / "latest.json"
|
|
203
|
+
if latest.exists():
|
|
204
|
+
data = _sanitize_snapshot_payload(json.loads(latest.read_text(encoding="utf-8")))
|
|
205
|
+
if data.get("session_id") == session_id or session_id == "latest":
|
|
206
|
+
return data
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def export_session_markdown(
|
|
211
|
+
*,
|
|
212
|
+
cwd: str | Path,
|
|
213
|
+
messages: list[ConversationMessage],
|
|
214
|
+
) -> Path:
|
|
215
|
+
"""Export the session transcript as Markdown."""
|
|
216
|
+
session_dir = get_project_session_dir(cwd)
|
|
217
|
+
path = session_dir / "transcript.md"
|
|
218
|
+
parts: list[str] = ["# OpenHarness Session Transcript"]
|
|
219
|
+
for message in messages:
|
|
220
|
+
parts.append(f"\n## {message.role.capitalize()}\n")
|
|
221
|
+
text = message.text.strip()
|
|
222
|
+
if text:
|
|
223
|
+
parts.append(text)
|
|
224
|
+
for block in message.tool_uses:
|
|
225
|
+
parts.append(f"\n```tool\n{block.name} {json.dumps(block.input, ensure_ascii=True)}\n```")
|
|
226
|
+
for block in message.content:
|
|
227
|
+
if getattr(block, "type", "") == "tool_result":
|
|
228
|
+
parts.append(f"\n```tool-result\n{block.content}\n```")
|
|
229
|
+
atomic_write_text(path, "\n".join(parts).strip() + "\n")
|
|
230
|
+
return path
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Simple token estimation utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def estimate_tokens(text: str) -> int:
|
|
7
|
+
"""Estimate tokens from plain text using a rough character heuristic."""
|
|
8
|
+
if not text:
|
|
9
|
+
return 0
|
|
10
|
+
return max(1, (len(text) + 3) // 4)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def estimate_message_tokens(messages: list[str]) -> int:
|
|
14
|
+
"""Estimate tokens for a collection of message strings."""
|
|
15
|
+
return sum(estimate_tokens(message) for message in messages)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Tool-output context budget helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
DEFAULT_TOOL_OUTPUT_INLINE_CHARS = 16_000
|
|
11
|
+
DEFAULT_TOOL_OUTPUT_PREVIEW_CHARS = 3_000
|
|
12
|
+
DEFAULT_MICROCOMPACT_TOOL_RESULT_CHARS = 4_000
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _read_positive_int_env(name: str, default: int, *, minimum: int = 1) -> int:
|
|
16
|
+
raw = os.environ.get(name, "").strip()
|
|
17
|
+
if not raw:
|
|
18
|
+
return default
|
|
19
|
+
try:
|
|
20
|
+
return max(minimum, int(raw))
|
|
21
|
+
except ValueError:
|
|
22
|
+
log.warning("Ignoring invalid %s=%r", name, raw)
|
|
23
|
+
return default
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def tool_output_inline_chars() -> int:
|
|
27
|
+
return _read_positive_int_env(
|
|
28
|
+
"OPENHARNESS_TOOL_OUTPUT_INLINE_CHARS",
|
|
29
|
+
DEFAULT_TOOL_OUTPUT_INLINE_CHARS,
|
|
30
|
+
minimum=256,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def tool_output_preview_chars() -> int:
|
|
35
|
+
return _read_positive_int_env(
|
|
36
|
+
"OPENHARNESS_TOOL_OUTPUT_PREVIEW_CHARS",
|
|
37
|
+
DEFAULT_TOOL_OUTPUT_PREVIEW_CHARS,
|
|
38
|
+
minimum=128,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def microcompact_tool_result_chars() -> int:
|
|
43
|
+
return _read_positive_int_env(
|
|
44
|
+
"OPENHARNESS_MICROCOMPACT_TOOL_RESULT_CHARS",
|
|
45
|
+
DEFAULT_MICROCOMPACT_TOOL_RESULT_CHARS,
|
|
46
|
+
minimum=256,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_microcompactable_tool_result(tool_name: str, content: str) -> bool:
|
|
51
|
+
"""Return True when a tool result should be eligible for old-result clearing."""
|
|
52
|
+
normalized = tool_name.strip()
|
|
53
|
+
if normalized.startswith("mcp__"):
|
|
54
|
+
return True
|
|
55
|
+
return len(content) >= microcompact_tool_result_chars()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Skill exports."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
8
|
+
from openharness.skills.registry import SkillRegistry
|
|
9
|
+
from openharness.skills.types import SkillDefinition
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SkillDefinition",
|
|
13
|
+
"SkillRegistry",
|
|
14
|
+
"discover_project_skill_dirs",
|
|
15
|
+
"get_user_skill_dirs",
|
|
16
|
+
"get_user_skills_dir",
|
|
17
|
+
"load_skill_registry",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __getattr__(name: str):
|
|
22
|
+
if name in {"discover_project_skill_dirs", "get_user_skill_dirs", "get_user_skills_dir", "load_skill_registry"}:
|
|
23
|
+
from openharness.skills.loader import (
|
|
24
|
+
discover_project_skill_dirs,
|
|
25
|
+
get_user_skill_dirs,
|
|
26
|
+
get_user_skills_dir,
|
|
27
|
+
load_skill_registry,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
"discover_project_skill_dirs": discover_project_skill_dirs,
|
|
32
|
+
"get_user_skill_dirs": get_user_skill_dirs,
|
|
33
|
+
"get_user_skills_dir": get_user_skills_dir,
|
|
34
|
+
"load_skill_registry": load_skill_registry,
|
|
35
|
+
}[name]
|
|
36
|
+
if name == "SkillRegistry":
|
|
37
|
+
from openharness.skills.registry import SkillRegistry
|
|
38
|
+
|
|
39
|
+
return SkillRegistry
|
|
40
|
+
if name == "SkillDefinition":
|
|
41
|
+
from openharness.skills.types import SkillDefinition
|
|
42
|
+
|
|
43
|
+
return SkillDefinition
|
|
44
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Shared YAML frontmatter parsing for SKILL.md files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_bool_frontmatter(value: Any, *, default: bool) -> bool:
|
|
14
|
+
"""Parse permissive YAML frontmatter booleans."""
|
|
15
|
+
if value is None:
|
|
16
|
+
return default
|
|
17
|
+
if isinstance(value, bool):
|
|
18
|
+
return value
|
|
19
|
+
normalized = str(value).strip().lower()
|
|
20
|
+
if normalized in {"1", "true", "yes", "on"}:
|
|
21
|
+
return True
|
|
22
|
+
if normalized in {"0", "false", "no", "off"}:
|
|
23
|
+
return False
|
|
24
|
+
return default
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def optional_frontmatter_str(value: Any) -> str | None:
|
|
28
|
+
"""Return a stripped string value, or ``None`` when absent/blank."""
|
|
29
|
+
if isinstance(value, str) and value.strip():
|
|
30
|
+
return value.strip()
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_skill_metadata(
|
|
35
|
+
default_name: str,
|
|
36
|
+
content: str,
|
|
37
|
+
*,
|
|
38
|
+
fallback_template: str = "Skill: {name}",
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
"""Extract metadata from a SKILL.md file.
|
|
41
|
+
|
|
42
|
+
Parses YAML frontmatter (``---`` delimited) via ``yaml.safe_load`` so that
|
|
43
|
+
folded block scalars (``>``), literal block scalars (``|``), quoted values,
|
|
44
|
+
and other standard YAML constructs are handled correctly. Falls back to
|
|
45
|
+
``# heading`` + first body paragraph when no usable frontmatter is present,
|
|
46
|
+
and finally to ``fallback_template`` when no description can be derived.
|
|
47
|
+
"""
|
|
48
|
+
name = default_name
|
|
49
|
+
description = ""
|
|
50
|
+
frontmatter: dict[str, Any] = {}
|
|
51
|
+
lines = content.splitlines()
|
|
52
|
+
|
|
53
|
+
if content.startswith("---\n"):
|
|
54
|
+
end_index = content.find("\n---\n", 4)
|
|
55
|
+
if end_index != -1:
|
|
56
|
+
try:
|
|
57
|
+
metadata = yaml.safe_load(content[4:end_index])
|
|
58
|
+
if isinstance(metadata, dict):
|
|
59
|
+
frontmatter = metadata
|
|
60
|
+
val = metadata.get("name")
|
|
61
|
+
if isinstance(val, str) and val.strip():
|
|
62
|
+
name = val.strip()
|
|
63
|
+
val = metadata.get("description")
|
|
64
|
+
if isinstance(val, str) and val.strip():
|
|
65
|
+
description = val.strip()
|
|
66
|
+
except yaml.YAMLError:
|
|
67
|
+
logger.debug("Failed to parse YAML frontmatter for skill %s", default_name)
|
|
68
|
+
|
|
69
|
+
if not description:
|
|
70
|
+
for line in lines:
|
|
71
|
+
stripped = line.strip()
|
|
72
|
+
if stripped.startswith("# "):
|
|
73
|
+
if not name or name == default_name:
|
|
74
|
+
name = stripped[2:].strip() or default_name
|
|
75
|
+
continue
|
|
76
|
+
if stripped and not stripped.startswith("---") and not stripped.startswith("#"):
|
|
77
|
+
description = stripped[:200]
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
if not description:
|
|
81
|
+
description = fallback_template.format(name=name)
|
|
82
|
+
return {"name": name, "description": description, "frontmatter": frontmatter}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse_skill_frontmatter(
|
|
86
|
+
default_name: str,
|
|
87
|
+
content: str,
|
|
88
|
+
*,
|
|
89
|
+
fallback_template: str = "Skill: {name}",
|
|
90
|
+
) -> tuple[str, str]:
|
|
91
|
+
"""Extract ``name`` and ``description`` from a SKILL.md file."""
|
|
92
|
+
metadata = parse_skill_metadata(
|
|
93
|
+
default_name,
|
|
94
|
+
content,
|
|
95
|
+
fallback_template=fallback_template,
|
|
96
|
+
)
|
|
97
|
+
return str(metadata["name"]), str(metadata["description"])
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Bundled skill definitions loaded from .md files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from openharness.skills._frontmatter import (
|
|
8
|
+
optional_frontmatter_str,
|
|
9
|
+
parse_bool_frontmatter,
|
|
10
|
+
parse_skill_frontmatter,
|
|
11
|
+
parse_skill_metadata,
|
|
12
|
+
)
|
|
13
|
+
from openharness.skills.types import SkillDefinition
|
|
14
|
+
|
|
15
|
+
_CONTENT_DIR = Path(__file__).parent / "content"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_bundled_skills() -> list[SkillDefinition]:
|
|
19
|
+
"""Load all bundled skills from the content/ directory."""
|
|
20
|
+
skills: list[SkillDefinition] = []
|
|
21
|
+
if not _CONTENT_DIR.exists():
|
|
22
|
+
return skills
|
|
23
|
+
for path in sorted(_CONTENT_DIR.glob("*.md")):
|
|
24
|
+
content = path.read_text(encoding="utf-8")
|
|
25
|
+
metadata = _parse_metadata(path.stem, content)
|
|
26
|
+
display_name = metadata["name"] if metadata["name"] != path.stem else None
|
|
27
|
+
skills.append(
|
|
28
|
+
SkillDefinition(
|
|
29
|
+
name=metadata["name"],
|
|
30
|
+
description=metadata["description"],
|
|
31
|
+
content=content,
|
|
32
|
+
source="bundled",
|
|
33
|
+
path=str(path),
|
|
34
|
+
base_dir=str(path.parent),
|
|
35
|
+
command_name=path.stem,
|
|
36
|
+
display_name=display_name,
|
|
37
|
+
user_invocable=metadata["user_invocable"],
|
|
38
|
+
disable_model_invocation=metadata["disable_model_invocation"],
|
|
39
|
+
model=metadata["model"],
|
|
40
|
+
argument_hint=metadata["argument_hint"],
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
return skills
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _parse_frontmatter(default_name: str, content: str) -> tuple[str, str]:
|
|
47
|
+
"""Extract name and description from a bundled skill markdown file.
|
|
48
|
+
|
|
49
|
+
Delegates to the shared parser so YAML block scalars (``>``, ``|``),
|
|
50
|
+
quoted values, and other standard YAML constructs are handled the same
|
|
51
|
+
way as user-installed skills.
|
|
52
|
+
"""
|
|
53
|
+
return parse_skill_frontmatter(
|
|
54
|
+
default_name,
|
|
55
|
+
content,
|
|
56
|
+
fallback_template="Bundled skill: {name}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_metadata(default_name: str, content: str) -> dict:
|
|
61
|
+
parsed = parse_skill_metadata(default_name, content, fallback_template="Bundled skill: {name}")
|
|
62
|
+
frontmatter = parsed.get("frontmatter")
|
|
63
|
+
if not isinstance(frontmatter, dict):
|
|
64
|
+
frontmatter = {}
|
|
65
|
+
return {
|
|
66
|
+
"name": str(parsed["name"]),
|
|
67
|
+
"description": str(parsed["description"]),
|
|
68
|
+
"user_invocable": parse_bool_frontmatter(frontmatter.get("user-invocable"), default=True),
|
|
69
|
+
"disable_model_invocation": parse_bool_frontmatter(
|
|
70
|
+
frontmatter.get("disable-model-invocation"),
|
|
71
|
+
default=False,
|
|
72
|
+
),
|
|
73
|
+
"model": optional_frontmatter_str(frontmatter.get("model")),
|
|
74
|
+
"argument_hint": optional_frontmatter_str(frontmatter.get("argument-hint")),
|
|
75
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# commit
|
|
2
|
+
|
|
3
|
+
Create clean, well-structured git commits.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
Use when the user asks to commit changes, create a PR, or prepare code for review.
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
1. Run `git status` and `git diff` to understand all changes
|
|
12
|
+
2. Analyze changes: categorize as feature, fix, refactor, docs, test, etc.
|
|
13
|
+
3. Draft a concise commit message:
|
|
14
|
+
- First line: imperative mood, under 72 chars, describes the "why"
|
|
15
|
+
- Body (if needed): explain context, trade-offs, or breaking changes
|
|
16
|
+
4. Stage only relevant files — never stage .env, credentials, or large binaries
|
|
17
|
+
5. Create the commit
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- Prefer specific `git add <file>` over `git add -A`
|
|
22
|
+
- Never use `--no-verify` unless explicitly asked
|
|
23
|
+
- Never amend published commits unless explicitly asked
|
|
24
|
+
- If a pre-commit hook fails, fix the issue and create a NEW commit (don't --amend)
|
|
25
|
+
- Include `Co-Authored-By` if pair programming
|