From 4764482c735159f470e5afbbcad51c519d46e6b3 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Sun, 5 Jul 2026 18:27:33 +0800 Subject: [PATCH] Harden Jyotish runtime evidence gates --- .codex-plugin/plugin.json | 47 ++ .gitignore | 1 + AGENTS.md | 26 ++ README.md | 64 +++ ...rnal_engine_blocker_research_2026_07_05.md | 49 ++ ..._output_acceptance_error_log_2026_07_04.md | 25 + ..._source_invocation_honesty_audit_latest.md | 61 +++ ...ce_runtime_consumption_audit_2026_07_03.md | 57 +++ ...ches_runtime_reference_audit_2026_07_03.md | 68 +++ ...e_integration_priority_table_2026_07_03.md | 197 ++++++++ docs/research/pre_work_error_ledger.md | 67 +++ ...tch1_runtime_retrieval_audit_2026_07_03.md | 35 ++ ...whole_machine_fragment_sweep_2026_07_05.md | 79 ++++ .../2026-07-03-local-codex-plugin-wrapper.md | 42 ++ ...7-03-open-source-p0-file-execution-plan.md | 273 +++++++++++ ...26-07-03-prashna-remedies-unified-entry.md | 84 ++++ ...6-07-03-vedastro-runtime-truth-contract.md | 105 +++++ ...26-07-04-bv-raman-report-book-structure.md | 68 +++ docs/vibe_coding_setup.md | 26 ++ findings.md | 20 + jyotish-app/index.html | 10 + jyotish-app/main.js | 188 +++++++- jyotish-app/style.css | 16 + mcp_server.py | 213 +++++++-- progress.md | 18 + scripts/diagnose_external_engine_adapters.py | 67 +++ scripts/diagnose_jyotishganit_adapter.py | 73 +++ scripts/diagnose_pyjhora_adapter.py | 65 +++ scripts/diagnose_vedastro_mode.py | 26 +- scripts/export_chart_research_pdf.py | 251 ++++++++++ scripts/historical_event_backtest.py | 6 + .../interpretation_source_inventory_gate.py | 70 +-- .../interpretation_source_runtime_coverage.py | 71 +++ scripts/jaimini.py | 25 + scripts/jyotish_api_server.py | 296 +++++++++++- scripts/kp_system.py | 101 ++++ scripts/muhurta.py | 54 +++ scripts/pre_work_check.py | 154 ++++++ scripts/preflight_fragment_scan.py | 23 + scripts/print_cline_mcp_config.py | 56 +++ scripts/remote_repo_visibility_check.py | 135 ++++++ scripts/run_quality_gate.py | 77 ++- scripts/sync_final_evidence_packet_status.py | 64 +++ scripts/unified_consultation_orchestrator.py | 442 +++++++++++++++++- scripts/vedastro_official_mcp_bridge.py | 47 +- scripts/vedastro_service_adapter.py | 7 + scripts/vedastro_user_entrypoint.py | 15 +- tests/test_api_server_security.py | 257 +++++++++- tests/test_bv_raman_report_book_acceptance.py | 167 +++++++ tests/test_chart_research_export_tools.py | 111 +++++ tests/test_codex_plugin_wrapper.py | 26 ++ ...est_external_engine_adapter_diagnostics.py | 33 ++ ..._final_jhora_evidence_packet_acceptance.py | 134 ++++++ tests/test_frontend_productization.py | 35 ++ ...ation_source_advanced_pipeline_contract.py | 6 +- ...st_interpretation_source_inventory_gate.py | 13 + ...terpretation_source_next_phase_contract.py | 65 +++ ..._interpretation_source_runtime_coverage.py | 33 ++ .../test_jyotishganit_adapter_diagnostics.py | 39 ++ tests/test_marriage_precision_extensions.py | 83 ++++ tests/test_mcp_strict_workflow_career.py | 99 +++- tests/test_mcp_strict_workflow_finance.py | 20 +- .../test_mcp_strict_workflow_relationship.py | 47 +- tests/test_muhurta.py | 19 + tests/test_pre_work_check.py | 39 ++ tests/test_preflight_fragment_scan.py | 137 +----- tests/test_project_fragment_governance.py | 56 +++ tests/test_pyjhora_adapter_diagnostics.py | 48 ++ tests/test_remote_repo_visibility_check.py | 25 + .../test_unified_consultation_orchestrator.py | 230 ++++++++- ...st_vedastro_external_technique_evidence.py | 12 + tests/test_vedastro_official_full_snapshot.py | 22 + tests/test_vedastro_official_mcp_bridge.py | 81 +++- .../test_vedastro_runtime_mode_diagnostics.py | 22 + tests/test_vedastro_user_entrypoint.py | 5 + tests/test_vibe_coding_setup.py | 53 +++ 76 files changed, 5628 insertions(+), 253 deletions(-) create mode 100644 .codex-plugin/plugin.json create mode 100644 docs/research/external_engine_blocker_research_2026_07_05.md create mode 100644 docs/research/final_output_acceptance_error_log_2026_07_04.md create mode 100644 docs/research/interpretation_source_invocation_honesty_audit_latest.md create mode 100644 docs/research/interpretation_source_runtime_consumption_audit_2026_07_03.md create mode 100644 docs/research/open_source_batches_runtime_reference_audit_2026_07_03.md create mode 100644 docs/research/open_source_integration_priority_table_2026_07_03.md create mode 100644 docs/research/pre_work_error_ledger.md create mode 100644 docs/research/real_case_batch1_runtime_retrieval_audit_2026_07_03.md create mode 100644 docs/research/whole_machine_fragment_sweep_2026_07_05.md create mode 100644 docs/superpowers/plans/2026-07-03-local-codex-plugin-wrapper.md create mode 100644 docs/superpowers/plans/2026-07-03-open-source-p0-file-execution-plan.md create mode 100644 docs/superpowers/plans/2026-07-03-prashna-remedies-unified-entry.md create mode 100644 docs/superpowers/plans/2026-07-03-vedastro-runtime-truth-contract.md create mode 100644 docs/superpowers/plans/2026-07-04-bv-raman-report-book-structure.md create mode 100644 docs/vibe_coding_setup.md create mode 100644 scripts/diagnose_external_engine_adapters.py create mode 100644 scripts/diagnose_jyotishganit_adapter.py create mode 100644 scripts/diagnose_pyjhora_adapter.py create mode 100644 scripts/export_chart_research_pdf.py create mode 100644 scripts/interpretation_source_runtime_coverage.py create mode 100644 scripts/pre_work_check.py create mode 100644 scripts/print_cline_mcp_config.py create mode 100644 scripts/remote_repo_visibility_check.py create mode 100644 scripts/sync_final_evidence_packet_status.py create mode 100644 tests/test_bv_raman_report_book_acceptance.py create mode 100644 tests/test_chart_research_export_tools.py create mode 100644 tests/test_codex_plugin_wrapper.py create mode 100644 tests/test_external_engine_adapter_diagnostics.py create mode 100644 tests/test_final_jhora_evidence_packet_acceptance.py create mode 100644 tests/test_interpretation_source_runtime_coverage.py create mode 100644 tests/test_jyotishganit_adapter_diagnostics.py create mode 100644 tests/test_marriage_precision_extensions.py create mode 100644 tests/test_pre_work_check.py create mode 100644 tests/test_project_fragment_governance.py create mode 100644 tests/test_pyjhora_adapter_diagnostics.py create mode 100644 tests/test_remote_repo_visibility_check.py create mode 100644 tests/test_vibe_coding_setup.py diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 00000000..a12d0df4 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,47 @@ +{ + "name": "jyotish-vedic-astrology", + "version": "6.9.14", + "description": "Jyotish local Codex plugin wrapper for Vedic astrology skills and MCP tools.", + "author": { + "name": "wuyongnaren", + "url": "https://github.com/732642856/yinduzhanxing" + }, + "homepage": "https://github.com/732642856/yinduzhanxing", + "repository": "https://github.com/732642856/yinduzhanxing", + "license": "MIT", + "keywords": [ + "jyotish", + "vedic-astrology", + "mcp", + "skills", + "codex" + ], + "skills": "./skills/", + "mcpServers": { + "jyotish": { + "command": "python3", + "args": [ + "./mcp_server.py" + ] + } + }, + "interface": { + "displayName": "印度占星", + "shortDescription": "Jyotish 技能 + MCP 本地插件包装", + "longDescription": "Use the existing Jyotish repo skills and MCP server inside Codex for chart reading, rectification, timing, prashna, and remedies workflows.", + "developerName": "wuyongnaren", + "category": "Developer Tools", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "websiteURL": "https://github.com/732642856/yinduzhanxing", + "defaultPrompt": [ + "用印度占星项目分析这个出生信息。", + "先做生时校正,再看婚恋与事业。", + "调用 Jyotish MCP 工具输出严格工作流结果。" + ], + "brandColor": "#7C3AED" + } +} diff --git a/.gitignore b/.gitignore index ea794e14..c2675f67 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ htmlcov/ .ruff_cache/ .mypy_cache/ .hypothesis/ +.cline/ benchmarks/jyotish/outputs/ output_report.txt results_extracted.md diff --git a/AGENTS.md b/AGENTS.md index feec7806..d0c04f66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,3 +76,29 @@ - MEVG / Global Web Evidence 或 Real Case Calibration 未完成 禁止把内部一致性伪装成“已经全球顶级精度”。 + +## 5. Pre-Work Error Ledger Hard Constraint + +为避免多窗口、多应用、多文件夹碎片导致重复误判,开工前 / 进行任何实质项目工作前必须读取: + +- `docs/research/pre_work_error_ledger.md` + +若任务涉及运行入口、镜像边界、外部 oracle、远端同步、适配器、测试验收或大规模资料治理,还必须读取: + +- `docs/research/whole_machine_fragment_sweep_2026_07_05.md` +- `docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md` + +同时必须运行: + +- `python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` + +该预检必须包含: + +- `scripts/diagnose_external_engine_adapters.py --json` + +执行要求: + +1. 不得把 `.workbuddy` 镜像当作运行主仓。 +2. 不得在 `git ls-remote` / fetch / push 等远端验证失败时声称云端已同步。 +3. 不得在未查看当前 `git status --short --branch` 时覆盖或重置本地变更。 +4. 新发现的重复错误、阻塞、碎片目录、远端验证失败,必须追加到错误台账或当轮 sweep 文档。 diff --git a/README.md b/README.md index 72585280..f482e658 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ VEDASTRO_ENABLE_NETWORK=1 默认运行是快速模式:VedAstro official 证据层如果没有在前台预算内闭环,会诚实标记 `official_snapshot_budget_exhausted` 并退回本地 Swiss Ephemeris。要跑 official extended 模式,复制 `.env.official.example` 为 `.env.local` 并填好 endpoint/network/key;运行 `python3 scripts/diagnose_vedastro_mode.py` 可先确认当前是 `fast_local_fallback` 还是 `official_extended`。 +AI/vibe coding 推荐入口:Cline 接本仓 MCP,Aider 负责低成本小改,Dyad 只做前端原型。运行 `python3 scripts/print_cline_mcp_config.py` 生成 Cline MCP 配置;详情见 `docs/vibe_coding_setup.md`。 + ### 中国大陆用户:VedAstro Gateway 模式 普通中国大陆用户不需要、也不应该让浏览器直连 VedAstro。推荐部署方式是:网页只访问你自己的本地或云端后端;后端通过 `VedAstro Gateway` 统一管理 self-host、official upstream、TTL/cache、free-tier queue 和 local fallback。 @@ -139,6 +141,68 @@ python3 scripts/vedastro_user_entrypoint.py \ 边界必须保留:这个入口**不会把 641 项全部当作已执行**。它先做官方能力目录分类和主题选择;能自动执行的进入证据层,需要第二人资料、用户文本、校时画像或官方网络预算的方法会保持 `needs_user_context`、`needs_user_text`、`needs_rectification_profile` 或 `blocked`。 +#### 作为 Codex 本地插件安装 + +本仓现在带了最小插件包装:`.codex-plugin/plugin.json`。它复用现有 `skills/` 与根目录 `mcp_server.py`,适合你把当前仓直接装进 Codex 本机环境。 + +1. 先把本仓登记到个人 marketplace: + +```bash +python3 /Users/wuyongnaren/.codex/skills/.system/plugin-creator/scripts/create_basic_plugin.py \ + jyotish-vedic-astrology \ + --path ~/.codex/plugins \ + --marketplace-path ~/.agents/plugins/marketplace.json \ + --marketplace-name personal \ + --with-marketplace +``` + +上面是 Codex 官方脚本的标准 marketplace 流。如果你要让 **当前仓本身** 被安装,关键不是用 scaffold 目录跑能力,而是让 `~/.agents/plugins/marketplace.json` 里的 `jyotish-vedic-astrology` 条目最终指向当前仓路径。 + +最少要确认这一条存在: + +```json +{ + "name": "jyotish-vedic-astrology", + "source": { + "source": "local", + "path": "/Users/wuyongnaren/Documents/印度占星" + } +} +``` + +2. 若本机还没把 personal marketplace 接进 Codex: + +```bash +codex plugin marketplace add ~/.agents/plugins/marketplace.json +``` + +3. 安装插件: + +```bash +codex plugin add jyotish-vedic-astrology@personal +``` + +4. 检查是否已被识别: + +```bash +codex plugin list +``` + +5. 开一个**新线程**再测试。Codex 只会在新线程里重新拾取新装的 skills / MCP。 + +#### 本地更新 / 重装 + +当你改了 `.codex-plugin/plugin.json`、`skills/` 或 `mcp_server.py`: + +```bash +python3 /Users/wuyongnaren/.codex/skills/.system/plugin-creator/scripts/update_plugin_cachebuster.py \ + /Users/wuyongnaren/Documents/印度占星 + +codex plugin add jyotish-vedic-astrology@personal +``` + +然后重新开新线程验证。 + 推荐的 official extended `.env.local` 示例: ```bash diff --git a/docs/research/external_engine_blocker_research_2026_07_05.md b/docs/research/external_engine_blocker_research_2026_07_05.md new file mode 100644 index 00000000..3b30f925 --- /dev/null +++ b/docs/research/external_engine_blocker_research_2026_07_05.md @@ -0,0 +1,49 @@ +# External Engine Blocker Research 2026-07-05 + +Purpose: record what can be fixed locally vs. what remains blocked by external engines, licensing, or credentials. + +## PyJHora / JHora + +- PyPI package checked: `PyJHora` +- Observed PyPI version: `4.8.7` +- Upstream GitHub search top match: `naturalstupid/PyJHora` +- Observed GitHub license: `AGPL-3.0` +- Local status: `scripts/diagnose_pyjhora_adapter.py --json` reports `missing_dependency: jhora` +- Local action now implemented: diagnostics expose install hint, ephemeris-data note, and AGPL boundary. + +Boundary: + +- Do not vendor PyJHora into this repo. +- Do not make PyJHora a required runtime dependency. +- Keep it as optional external benchmark / parity oracle. + +Install hint for a separate benchmark environment: + +```bash +pip install PyJHora +``` + +## VedAstro + +- Public site checked: `https://vedastro.org/` returned reachable. +- `https://api.vedastro.org/api` returned HTTP 404 for a direct root probe. This only proves host reachability; it does not prove a full-snapshot endpoint is configured. +- Current local diagnostic: `scripts/diagnose_external_engine_adapters.py --json` reports VedAstro `available` in `official_extended` mode but with `premium_key_missing`. + +Closure rule: + +- Do not claim VedAstro cloud closure unless `vedastro_official.raw_response` is present in the runtime evidence packet. +- Free tier can remain `blocked` or `partial`; API key is recommended for stable official full snapshot. + +## Current Aggregate Status + +- VedAstro: `available`, blocker `premium_key_missing` +- PyJHora/JHora: `missing_dependency`, blocker `jhora` +- jyotishganit: `available`, license `MIT` +- Three-engine parity: `partial`, not complete. + +## Sources Checked + +- PyPI: `https://pypi.org/project/PyJHora/` +- GitHub search/API result: `https://github.com/naturalstupid/PyJHora` +- VedAstro site: `https://vedastro.org/` +- VedAstro API host probe: `https://api.vedastro.org/api` diff --git a/docs/research/final_output_acceptance_error_log_2026_07_04.md b/docs/research/final_output_acceptance_error_log_2026_07_04.md new file mode 100644 index 00000000..168afc55 --- /dev/null +++ b/docs/research/final_output_acceptance_error_log_2026_07_04.md @@ -0,0 +1,25 @@ +# Final Output Acceptance Error Log - 2026-07-04 + +Purpose: read this log before editing final report / evidence packet artifacts. Keep errors here so the same status drift does not repeat. + +## Known Errors + +| ID | Error | Seen In | Impact | Prevention | +|---|---|---|---|---| +| FOA-001 | status metadata drift | `jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v13.json` and later packet chain | Top-level packet said final, but metadata stayed stale/draft or old version. | `tests/test_final_jhora_evidence_packet_acceptance.py` asserts top-level status, metadata status, `current_version`, `packet_version`, and `canonical_packet`. | +| FOA-002 | canonical packet drift | latest packet advanced to v24 while metadata still pointed at v15 | Agents may cite old packet as current truth. | Latest-version acceptance resolves numeric highest packet and requires metadata + ledger to point to that file. | +| FOA-003 | stale next-step ledger | ledger still said next useful step was Chapter 04 after chapters 04-06, PDF, and raw appendix existed | User/operator can continue wrong workstream. | Ledger acceptance rejects the old Chapter 04 next-step phrase and requires an Acceptance/error-log gate next step. | +| FOA-004 | blank PDF artifact | prior ReportLab PDF attempt was blank/invalid and discarded | A report artifact can exist but be unusable. | Acceptance requires PDF path, HTML path, rendered QA pages, and non-trivial file sizes. | +| FOA-005 | chained latest-packet drift | `jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v25.json` appeared after v24, then v26 appeared after v25 while metadata still pointed one version behind | Fixing only one packet can become stale as later packet versions appear. | Acceptance always resolves the numeric highest packet and checks that exact packet, not a hard-coded version. | +| FOA-006 | wrong quality-gate CLI flag | `python3 scripts/run_quality_gate.py --runtime-truth` | The gate does not run; command exits with argument error. | Use `python3 scripts/run_quality_gate.py --profile runtime-truth`. | +| FOA-007 | long runtime-truth gate timeout | `python3 scripts/run_quality_gate.py --profile runtime-truth` exited 124 after 120s in this Codex tool session | A broad gate may not provide timely evidence inside the current command window. | Run focused acceptance first, then split runtime-truth into compile, inventory, diagnostics, and pytest target groups. | +| FOA-008 | captured quality-gate output hid the slow step | `scripts/run_quality_gate.py` used `capture_output=True` for child commands | Long gates appeared blank until timeout, making the stuck subcommand invisible. | `run()` now streams child output directly. | + +## Work Rule + +Before changing `scratch/local/pdf_review_123456/` final artifacts: + +1. Read this file. +2. Run `python3 -m pytest -q tests/test_final_jhora_evidence_packet_acceptance.py`. +3. For the broader runtime gate, prefer `PYTHONUNBUFFERED=1 python3 scripts/run_quality_gate.py --profile runtime-truth`; if it times out, split the gate into compile, inventory, diagnostics, and pytest target groups. +4. If it fails, record the new error here before patching status/docs. diff --git a/docs/research/interpretation_source_invocation_honesty_audit_latest.md b/docs/research/interpretation_source_invocation_honesty_audit_latest.md new file mode 100644 index 00000000..f6d1ed2c --- /dev/null +++ b/docs/research/interpretation_source_invocation_honesty_audit_latest.md @@ -0,0 +1,61 @@ +# Interpretation Source Invocation Honesty Audit Latest + +Date: 2026-07-03 + +## Verdict + +There are still local Jyotish information assets that have not been fully proven as invoked by the runtime interpretation chain. + +This audit does not claim every character has been semantically read and promoted. It records the current provable boundary. + +## Fast Evidence + +- `references/` files: 535 +- `references/open_source_sources/` files: 299 +- `references/real_case_studies/` files: 5 +- `docs/research/` files: 543 +- non-standard / binary / notebook / config assets under `references docs/research`: 53 + +Existing inventory / grading artifacts found: + +- `docs/research/character_level_inventory_manifest_latest.md` +- `docs/research/character_level_external_manifest_latest.md` +- `docs/research/character_level_source_grading_batch1_latest.md` +- `docs/research/interpretation_source_full_classification_2026_07_02.md` +- `docs/research/interpretation_source_priority1_batch1_promotion_audit_2026_07_02.md` + +These prove that character-level indexing and batch1 grading exist. They do not prove full runtime invocation for every local source asset. + +## Not Fully Closed + +1. Open-source source trees are not all proven as interpretation sources: + - `references/open_source_sources/jyotishganit` + - `references/open_source_sources/jaimini-tropical` + - `references/open_source_sources/VedicAstro` notebooks/test suites + - `references/open_source_sources/rishi-ai-mcp` + - `references/open_source_sources/vedic-astro-skills` + - `references/open_source_sources/dashaflow` + +2. Non-standard assets require separate extraction before source grading: + - `.ipynb` notebooks + - images + - config/rule files + - lockfiles / package metadata + - generated PDFs or PDF-related assets if present in later scans + +3. `docs/research/` remains mostly research/history/draft territory. It must stay classified and cannot automatically override runtime truth. + +4. Existing source grading is batch-based. Batch1 exists; full priority1 and all candidate assets are not proven closed. + +## Required Next Step + +Build a fast, reusable coverage tool that maps every candidate source file to: + +- extraction status +- source grade +- promotion status +- runtime source-pack visibility +- strict workflow visibility +- exclusion reason when not promoted + +Then run it in CI/quality gate so this question has a machine-checkable answer. diff --git a/docs/research/interpretation_source_runtime_consumption_audit_2026_07_03.md b/docs/research/interpretation_source_runtime_consumption_audit_2026_07_03.md new file mode 100644 index 00000000..6ef843d8 --- /dev/null +++ b/docs/research/interpretation_source_runtime_consumption_audit_2026_07_03.md @@ -0,0 +1,57 @@ +# Interpretation Source Runtime Consumption Audit 2026-07-03 + +## Scope + +Follow-up to the existing inventory gate and advanced pipeline contract. This audit checks whether already-classified interpretation source layers are consumed by strict workflows without rebuilding the inventory system. + +## Result + +- `scripts/interpretation_source_inventory_gate.py`: pass. +- Candidate pool: 956. +- Unclassified candidates: 0. +- Source pack status: used. +- Runtime strict workflows now assert consumption of the promoted batch2 topic layers via secondary context markers. + +## Consumed Runtime Markers + +The career, finance, and relationship strict workflow tests now require: + +- `dasha_timing_layer_used` +- `varga_strength_layer_used` +- `annual_special_layer_context` +- `modifier_obstacle_layer_used` + +These markers prove that the batch2 topic layer is not merely visible in the source pack; it is carried into adjudication context. + +## Tests Updated + +- `tests/test_mcp_strict_workflow_career.py` +- `tests/test_mcp_strict_workflow_finance.py` +- `tests/test_mcp_strict_workflow_relationship.py` + +The tests no longer require `secondary_context` to equal an old fixed list. They now require the original business signals plus the promoted source-layer markers. + +## Verification + +Passed: + +```bash +python3 -m pytest -q \ + tests/test_mcp_strict_workflow_career.py \ + tests/test_mcp_strict_workflow_finance.py \ + tests/test_mcp_strict_workflow_relationship.py \ + tests/test_interpretation_source_inventory_gate.py \ + tests/test_interpretation_source_advanced_pipeline_contract.py \ + tests/test_interpretation_source_next_phase_contract.py \ + tests/test_interpretation_source_core5_strict_visibility.py +``` + +Result: 94 passed. + +Also passed: `git diff --check`. + +## Boundary + +No runtime source-pack rebuild was done. No quarantined or draft sources were promoted. + +`python3 scripts/run_quality_gate.py --profile quick ...` exceeded the 120s command timeout in this session, so the quick gate was not used as completion evidence. diff --git a/docs/research/open_source_batches_runtime_reference_audit_2026_07_03.md b/docs/research/open_source_batches_runtime_reference_audit_2026_07_03.md new file mode 100644 index 00000000..6e1a46b0 --- /dev/null +++ b/docs/research/open_source_batches_runtime_reference_audit_2026_07_03.md @@ -0,0 +1,68 @@ +# Open Source Batches Runtime Reference Audit 2026-07-03 + +## Scope + +Advance the next queued open-source reference batches without turning them into primary truth: + +- `rishi_ai_mcp_batch1` +- `vedic_astro_skills_batch1` + +Also expose the remaining external gaps requested by the user: + +- VedAstro official default closure +- external oracle parity +- install / usage path slimming + +## Runtime Wiring + +`mcp_server._existing_interpretation_source_pack()` now exposes: + +- `rishi_ai_mcp_batch1_layer` + - status: `available` + - source refs: 16 + - mapped domains: career, finance, relationship, children, health, full_reading + - runtime truth status: `not_primary_truth` + +- `vedic_astro_skills_batch1_layer` + - status: `available` + - source refs: 8 + - mapped domains: core, reader_validation, career, relationship, rectification, calculator + - runtime truth status: `not_primary_truth` + +- `external_closure_gap_layer` + - `vedastro_official.status = blocked` + - `oracle_parity.status = blocked` + - `install_usage_path.status = needs_slimming` + +## Queue Update + +Completed as visible reference layers: + +- `real_case_studies_batch1` +- `rishi_ai_mcp_batch1` +- `vedic_astro_skills_batch1` + +Remaining queue: + +- `references_batch2` +- `vedastro_official_default_closure` +- `external_oracle_parity_batch` +- `install_usage_path_slimming` + +## Classification Boundary + +Open-source sources remain classified as `open_source_reference` unless they were already governed runtime refs such as existing QA / reader / yoga resources. This prevents new open-source material from overriding local strict rules or oracle-calibrated calculations. + +## Verification + +Passed: + +```bash +python3 -m pytest -q \ + tests/test_interpretation_source_inventory_gate.py \ + tests/test_interpretation_source_next_phase_contract.py \ + tests/test_interpretation_source_advanced_pipeline_contract.py \ + tests/test_mcp_strict_workflow_career.py \ + tests/test_mcp_strict_workflow_finance.py \ + tests/test_mcp_strict_workflow_relationship.py +``` diff --git a/docs/research/open_source_integration_priority_table_2026_07_03.md b/docs/research/open_source_integration_priority_table_2026_07_03.md new file mode 100644 index 00000000..d447a4d3 --- /dev/null +++ b/docs/research/open_source_integration_priority_table_2026_07_03.md @@ -0,0 +1,197 @@ +# 开源项目接入优先级落地表 + +日期: 2026-07-03 + +目的: 把仓内已经存在的开源镜像、黑盒基准、官方桥接入口,整理成一张可执行落地表。只写当前仓库里有真实文件可指认的对象,不靠聊天记忆。 + +边界先钉死: + +- 主运行链 source of truth 仍是本仓 `scripts/`、`mcp_server.py`、`references/`、`tests/`、`jyotish-app/`。 +- `.workbuddy` 不是 runtime truth。 +- `PyJHora` 只能走黑盒输出证据路径,不能把 AGPL 代码抄进 permissive 主链。 +- `open_source_reference` 可以进参考层、审计层、提示层,不自动升级成 primary truth。 + +相关现状依据: + +- 主链地图: [docs/research/unique_main_chain_map_2026_07_01.md](/Users/wuyongnaren/Documents/印度占星/docs/research/unique_main_chain_map_2026_07_01.md) +- 开源参考层已挂载说明: [docs/research/open_source_batches_runtime_reference_audit_2026_07_03.md](/Users/wuyongnaren/Documents/印度占星/docs/research/open_source_batches_runtime_reference_audit_2026_07_03.md) +- 已接入 smoke test: [tests/test_open_source_integrations.py](/Users/wuyongnaren/Documents/印度占星/tests/test_open_source_integrations.py) + +## 状态定义 + +- `已接`: 已有代码进入主仓脚本或 runtime reference pack,且有测试/文档证明。 +- `半接`: 仓里有镜像或桥接脚本,但还没压进默认主链,或只在参考层可见。 +- `未接`: 仓里有资料,但当前没有实际接线点。 + +## 优先级总表 + +| 优先级 | 开源对象(仓内真实文件) | 当前状态 | 接哪里 | 用来干什么 | 许可证风险 | 预计几小时落地 | +|---|---|---|---|---|---|---| +| P0 | `references/open_source_sources/panchanga_api/README.md` `references/open_source_sources/panchanga_api/MCP.md` `references/open_source_sources/panchanga_api/SKILL.md` | 半接 | `scripts/prashna.py` `scripts/remedies.py` `scripts/jyotish_api_server.py` `scripts/unified_consultation_orchestrator.py` `jyotish-app/main.js` | 把“时间问事 / Muhurta / Panchanga / Remedies”压到统一入口,复用你刚接好的 `prashna` 与 `audited_remedies` 主链 | 低。镜像内 README 指向 MIT;当前仓内有 `LICENSE` | 4-8h | +| P0 | `scripts/vedastro_service_adapter.py` `scripts/vedastro_official_mcp_bridge.py` `scripts/diagnose_vedastro_mode.py` | 半接 | `scripts/jyotish_api_server.py` `mcp_server.py` `scripts/unified_consultation_orchestrator.py` | 让 official layer 真正默认参与,而不是只做桥接存在证明;同时把 runtime truth 里的 `official/full/partial/fallback` 说清楚 | 低。这里主要是官方 API/桥接,不是复制外部 GPL 代码 | 3-6h | +| P0 | `references/oracle/artifacts/pyjhora_*` `references/oracle/artifacts/pending_packets/*pyjhora*.json` `scripts/generate_pyjhora_oracle_artifact_manifest.py` | 已接(黑盒证据层) | `scripts/external_oracle_sanity_closure.py` `scripts/oracle_benchmark_inventory.py` `scripts/historical_event_backtest.py` `README.md` | 保留 PyJHora 作为黑盒对照、历史事件回测、数值 sanity,不碰源码复制 | 中高。AGPL 风险不在“调用输出”,而在“复制代码/常量/实现” | 1-3h 做下一轮批量证据清点;8-16h 做更完整事件包 | +| P1 | `references/open_source_sources/jyotishganit/README.md` `references/open_source_sources/jyotishganit/LICENSE` `references/open_source_sources/jyotishganit/jyotishganit/*` | 已接(部分) | `scripts/bhava_bala.py` `scripts/shadbala.py` `scripts/shadbala_advanced.py` `scripts/trimshamsa_d30.py` `scripts/constants/mit_imported_constants.py` `scripts/jyotish_engine.py` | 继续把 MIT 安全算法/常量吸收到本地精度层,尤其是 `Bhava Bala`、`Sputa Drishti`、部分 divisional / constants 对齐 | 低。MIT,且本仓已有显式标注“基于 jyotishganit” | 2-6h 做一轮定点补强;10-20h 做系统 sweep | +| P1 | `references/open_source_sources/jaimini-tropical/README.md` `references/open_source_sources/jaimini-tropical/LICENSE` | 已接(部分) | `scripts/jaimini.py` `scripts/argala.py` `tests/test_open_source_integrations.py` `references/strict-workflow-router.md` | 补强 Jaimini/Arudha/Argala 这条线,让婚恋、事业、财富裁决里对 `AL/UL/Karaka/Argala` 的引用更硬 | 低。MIT | 2-5h | +| P1 | `references/open_source_sources/dashaflow/*` | 已接(部分),但镜像证据不完整 | `scripts/jaimini.py` `scripts/synastry.py` `tests/test_open_source_integrations.py` | 继续利用现成 `Arudha Pada`、附加合婚 kutas、Jaimini 辅助能力;少写重复代码 | 中。当前镜像目录里没看到本地 `LICENSE` 文件,虽在别的 README 中被描述为 MIT,也要补 upstream commit + license 证据 | 2-4h 补许可证锚点;4-8h 做能力扩展 | +| P1 | `references/open_source_sources/VedicAstro/README.md` `references/open_source_sources/VedicAstro/vedicastro/horary_chart.py` `references/open_source_sources/VedicAstro/test_suite/horary_functions_test.py` | 半接 | `scripts/prashna.py` `scripts/kp_system.py` `scripts/ephemeris_backend_probe.py` `tests/` 新 smoke | 作为 KP/Horary 参考源,补“时间问事”与 KP sub-lord 边界,不直接重写主引擎 | 低。MIT | 3-6h | +| P2 | `references/open_source_sources/vedic-astro-skills/README.md` `references/open_source_sources/vedic-astro-skills/antigravity/skills/...` | 半接(参考层已可见) | `mcp_server.py::_existing_interpretation_source_pack()` `references/strict-workflow-router.md` `jyotish-app` prompt pack / guided topics | 主要拿方法学、问答流程、reader validation、rectification 提示结构;不当计算真值层 | 低。MIT | 2-4h | +| P2 | `references/open_source_sources/rishi-ai-mcp/README.md` `references/open_source_sources/rishi-ai-mcp/rishi_ai_mcp.py` | 半接(参考层已可见) | `mcp_server.py::_existing_interpretation_source_pack()` `references/` 主题词映射 `prompt pack` | 拿主题分流、问答组织、relationship/finance/career topic taxonomy;不替代本地 strict workflow | 低。MIT | 2-4h | + +## 一条一条展开 + +### 1. `panchanga_api` → 最该先接 + +仓内文件: + +- [references/open_source_sources/panchanga_api/README.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/panchanga_api/README.md) +- [references/open_source_sources/panchanga_api/MCP.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/panchanga_api/MCP.md) +- [references/open_source_sources/panchanga_api/SKILL.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/panchanga_api/SKILL.md) + +为什么优先: + +- 你现在已经有 [scripts/prashna.py](/Users/wuyongnaren/Documents/印度占星/scripts/prashna.py)、[scripts/remedies.py](/Users/wuyongnaren/Documents/印度占星/scripts/remedies.py)、统一入口 [scripts/unified_consultation_orchestrator.py](/Users/wuyongnaren/Documents/印度占星/scripts/unified_consultation_orchestrator.py)。 +- 缺的不是“再发明一套 Prashna/Remedies”,而是把现有入口压得更完整。 +- `panchanga_api` 文档本身就覆盖 `prashna`、`muhurta`、`vrata`、`remedies`,跟你当前三入口结构天然贴合。 + +最省事接法: + +1. `prashna` 入口复用现有 `entry_mode == "prashna"`。 +2. `remedies` 继续只吃 `strict_audit_gate` 过滤后的输入。 +3. 单独补一个 `muhurta/panchanga` 轻 sidecar,不碰主 chart engine。 + +### 2. VedAstro official bridge → 必须继续压实 + +仓内文件: + +- [scripts/vedastro_service_adapter.py](/Users/wuyongnaren/Documents/印度占星/scripts/vedastro_service_adapter.py) +- [scripts/vedastro_official_mcp_bridge.py](/Users/wuyongnaren/Documents/印度占星/scripts/vedastro_official_mcp_bridge.py) +- [scripts/diagnose_vedastro_mode.py](/Users/wuyongnaren/Documents/印度占星/scripts/diagnose_vedastro_mode.py) + +为什么优先: + +- 这不是“有没有桥”。桥已经有。 +- 真缺口是: 默认主链什么时候自动用官方层、什么时候 cache 命中、什么时候 free-tier queue、什么时候 fallback。 +- 这条一旦压实,web / MCP / skill 才会统一口径。 + +最省事接法: + +1. 继续只走现有 adapter / official bridge。 +2. 不新增第二套 VedAstro planner。 +3. 把 `runtime_truth` 直接下沉到前端结果页、MCP strict 输出、full-reading。 + +### 3. PyJHora → 只许黑盒,不许抄 + +仓内文件: + +- [references/oracle/artifacts/pyjhora_oracle_artifact_manifest.json](/Users/wuyongnaren/Documents/印度占星/references/oracle/artifacts/pyjhora_oracle_artifact_manifest.json) +- [scripts/generate_pyjhora_oracle_artifact_manifest.py](/Users/wuyongnaren/Documents/印度占星/scripts/generate_pyjhora_oracle_artifact_manifest.py) +- 多个 `references/oracle/artifacts/pyjhora_*.txt` + +当前定位: + +- 它已经在 repo 里,但不是“可接入 runtime 的代码镜像”。 +- 它是“黑盒 external evidence 层”。 + +最省事接法: + +1. 继续扩证据包,不扩源码依赖。 +2. 让 `historical_event_backtest`、`oracle parity`、`benchmark dashboard` 直接吃 artifact。 +3. 不把任何 PyJHora 常量表、实现细节搬进 `scripts/`。 + +### 4. `jyotishganit` → 已有真吸收,继续吃 MIT 安全部分 + +仓内文件: + +- [references/open_source_sources/jyotishganit/README.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/jyotishganit/README.md) +- [references/open_source_sources/jyotishganit/LICENSE](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/jyotishganit/LICENSE) +- 已接痕迹: + - [scripts/bhava_bala.py](/Users/wuyongnaren/Documents/印度占星/scripts/bhava_bala.py) + - [scripts/shadbala.py](/Users/wuyongnaren/Documents/印度占星/scripts/shadbala.py) + - [scripts/shadbala_advanced.py](/Users/wuyongnaren/Documents/印度占星/scripts/shadbala_advanced.py) + - [scripts/trimshamsa_d30.py](/Users/wuyongnaren/Documents/印度占星/scripts/trimshamsa_d30.py) + - [scripts/constants/mit_imported_constants.py](/Users/wuyongnaren/Documents/印度占星/scripts/constants/mit_imported_constants.py) + +为什么适合继续接: + +- 这条已经不是“设想”。 +- 本仓已经明确写了“基于 jyotishganit (MIT License) 算法适配”。 +- 所以这里最值钱的是继续定点补 math,不是重新造壳。 + +### 5. `jaimini-tropical` + `dashaflow` → 已经接进脚本层,值得继续榨干 + +仓内文件: + +- [references/open_source_sources/jaimini-tropical/README.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/jaimini-tropical/README.md) +- [references/open_source_sources/jaimini-tropical/LICENSE](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/jaimini-tropical/LICENSE) +- [references/open_source_sources/dashaflow/](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/dashaflow) +- 已接证明: [tests/test_open_source_integrations.py](/Users/wuyongnaren/Documents/印度占星/tests/test_open_source_integrations.py) +- 当前脚本: + - [scripts/jaimini.py](/Users/wuyongnaren/Documents/印度占星/scripts/jaimini.py) + - [scripts/argala.py](/Users/wuyongnaren/Documents/印度占星/scripts/argala.py) + - [scripts/synastry.py](/Users/wuyongnaren/Documents/印度占星/scripts/synastry.py) + +为什么不是 P0: + +- 这条已经有接入 smoke test。 +- 现在更大的缺口不在“有没有 A1/A10/UL/Argala”,而在“这些结果有没有被 strict workflow 主裁决强制消费”。 + +特殊风险: + +- `jaimini-tropical` 本地有 MIT `LICENSE`。 +- `dashaflow` 当前镜像目录未见本地 `LICENSE`。虽然现有文档多处把它当 MIT 参考,但落下一步代码前,应先补一条 canonical license 锚点。 + +### 6. `VedicAstro` → 适合做 Horary/KP 辅助,不适合夺主链 + +仓内文件: + +- [references/open_source_sources/VedicAstro/README.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/VedicAstro/README.md) +- [references/open_source_sources/VedicAstro/vedicastro/horary_chart.py](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/VedicAstro/vedicastro/horary_chart.py) +- [references/open_source_sources/VedicAstro/test_suite/horary_functions_test.py](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/VedicAstro/test_suite/horary_functions_test.py) + +为什么排 P1: + +- 你仓里已有 [scripts/prashna.py](/Users/wuyongnaren/Documents/印度占星/scripts/prashna.py) 和 [scripts/kp_system.py](/Users/wuyongnaren/Documents/印度占星/scripts/kp_system.py)。 +- 所以更值钱的是拿它校一遍 Horary/KP 边界,不是整包搬迁。 + +### 7. `vedic-astro-skills` / `rishi-ai-mcp` → 继续留在参考层,别冒充真值层 + +仓内文件: + +- [references/open_source_sources/vedic-astro-skills/README.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/vedic-astro-skills/README.md) +- [references/open_source_sources/rishi-ai-mcp/README.md](/Users/wuyongnaren/Documents/印度占星/references/open_source_sources/rishi-ai-mcp/README.md) + +当前已接现状: + +- 参考层已挂进 [mcp_server.py](/Users/wuyongnaren/Documents/印度占星/mcp_server.py) 的 `interpretation_source_pack` +- 研究文档已明确它们是 `not_primary_truth` + +为什么不该抢前排: + +- 它们更像“问答结构 / topic taxonomy / prompt discipline / reader validation”的参考材料。 +- 不适合替代本地 math、official evidence、strict audit gate。 + +## 最省算力的执行顺序 + +1. `panchanga_api` → 接 `prashna/remedies/muhurta` 统一入口 +2. VedAstro official bridge → 压实默认主链 official 优先 +3. `jyotishganit` → 做定点 math 补强 +4. `jaimini-tropical` / `dashaflow` → 让已有结果被 strict workflow 真消费 +5. `VedicAstro` → 做 Horary/KP smoke + 边界校验 +6. `vedic-astro-skills` / `rishi-ai-mcp` → 只升格高价值 reader/rectification 规则 +7. PyJHora → 继续只做黑盒证据扩容 + +## 我给你的直白建议 + +如果只选 3 项,最值钱的是: + +1. `panchanga_api` +2. VedAstro official bridge default closure +3. `jyotishganit` 定点精度补强 + +原因很简单: + +- 第 1 项补“入口能力” +- 第 2 项补“官方优先” +- 第 3 项补“本地精度” + +这 3 项一起做,才是真正对用户可用性和严谨度都有立刻收益。 diff --git a/docs/research/pre_work_error_ledger.md b/docs/research/pre_work_error_ledger.md new file mode 100644 index 00000000..7bfed02f --- /dev/null +++ b/docs/research/pre_work_error_ledger.md @@ -0,0 +1,67 @@ +# Pre-Work Error Ledger + +Purpose: read this file before substantial project work. It exists to stop repeat mistakes caused by multiple Codex windows, WorkBuddy mirrors, local drafts, backup folders, and partial cloud-git visibility. + +## Mandatory Pre-Work Check + +Run or consciously verify: + +```bash +sed -n '1,220p' AGENTS.md +sed -n '1,260p' docs/research/pre_work_error_ledger.md +git status --short --branch +git remote -v +python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45 +``` +The one-command gate must cover: +- `tests/test_runtime_import_boundaries.py` +- `tests/test_project_fragment_governance.py` +- `tests/test_preflight_fragment_scan.py` +- `tests/test_remote_repo_visibility_check.py` +- `tests/test_pre_work_check.py` + +For large architecture or release work, also read: + +- `docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md` +- `docs/research/whole_machine_fragment_sweep_2026_07_05.md` +- `docs/research/unique_main_chain_map_2026_07_01.md` +- `docs/research/local_drafts_2026_06_disposition.md` + +## Error Ledger + +| ID | Error / Risk | Current Status | Required Guard | +|---|---|---|---| +| ERR-001 | Runtime code pulled modules from `.workbuddy` distribution mirror. | resolved | Keep `tests/test_runtime_import_boundaries.py`; `.workbuddy` is reference only. | +| ERR-002 | `mcp_server.py` documentation implied `.workbuddy` was a runtime path. | resolved | MCP top docs must point to main repo runtime; mirror wording must say distribution/reference only. | +| ERR-003 | `local_drafts` mixed high-value research with disposable drafts. | mitigated | Use `docs/research/local_drafts_2026_06_disposition.md`; do not delete or promote drafts ad hoc. | +| ERR-004 | Multiple local folders contain Jyotish fragments, older adapters, and WorkBuddy mirrors. | active | Read latest fragment sweep before changing adapters, oracle paths, or skill runtime boundaries. | +| ERR-005 | Terminal git access to GitHub can fail even when the browser can open the repo. | active | Use `python3 scripts/remote_repo_visibility_check.py`; do not claim cloud sync unless status is `verified`. | +| ERR-006 | Whole-machine `find` can time out when run too broadly. | active | Use split scans by directory and bounded `-maxdepth`; record timeouts as findings. | +| ERR-007 | `.workbuddy/skills/jyotish-vedic-astrology` is a dirty historical mirror. | active | Never copy it over main repo. Use only for read-only comparison. | +| ERR-008 | Current main workspace may be dirty with user/Codex changes. | active | Never reset or checkout. Read touched files before editing. | +| ERR-009 | Astrology interpretation can drift into story-fitting from conversation history. | active | For blind technical reports, use only declared evidence packets/PDF and mark MEVG/real-case gaps. | +| ERR-010 | VedAstro official/cloud evidence can be partial or dasha-conflicting. | active | Mark official closure partial unless exact endpoint/settings and raw output are verified. | +| ERR-011 | Bare `pytest` command may be missing from PATH even when pytest module exists. | active | Use `python3 -m pytest ...` in acceptance commands. | +| ERR-012 | Governance tests can fail if wording is narrower than the actual guardrail. | observed 2026-07-05 | Keep tests tied to explicit user-facing terms such as `开工前` and the ledger path. | +| ERR-013 | GitHub API can fail from terminal Python even when browser/Web can open GitHub. | active | Treat `remote_repo_visibility_check.py` status `blocked` as authoritative for terminal parity; browser visibility alone is not sync proof. | +| ERR-014 | Pre-work checks can be skipped when split across several manual commands. | mitigated | Use `python3 scripts/pre_work_check.py` as the one-command pre-work gate. | +| ERR-015 | One-command pre-work can exceed desktop outer timeout if child command timeout is too wide or scan runs twice. | observed 2026-07-05 | Keep pytest child timeout bounded at 45s; let `pre_work_check.py` cache `preflight_fragment_scan.py` output for `tests/test_preflight_fragment_scan.py`. | +| ERR-016 | Full `tests/test_api_server_security.py` can exceed the desktop outer timeout. | observed 2026-07-05 | Use focused API test slices during development; reserve full API file run for longer verification windows. | +| ERR-017 | Pre-work gate could pass without checking the older Round 25 fragment sweep or aggregate external-engine adapter diagnostics. | mitigated 2026-07-05 | `scripts/pre_work_check.py` must require both whole-machine sweep docs and run `scripts/diagnose_external_engine_adapters.py --json` before substantial work. | +| ERR-018 | External engine blockers can be described verbally but not carried into diagnostics. | mitigated 2026-07-05 | `diagnose_external_engine_adapters.py` must expose VedAstro closure plan and PyJHora/JHora install/license/ephemeris boundary; keep `docs/research/external_engine_blocker_research_2026_07_05.md` current. | + +## Fragment Sweep Command Set + +Use split scans, not one unbounded full-home command: + +```bash +for d in /Users/wuyongnaren/Documents /Users/wuyongnaren/WorkBuddy /Users/wuyongnaren/.workbuddy /Users/wuyongnaren/Downloads /Users/wuyongnaren/Desktop /Users/wuyongnaren/.codex/attachments; do + [ -d "$d" ] && find "$d" -maxdepth 6 -type d -name .git 2>/dev/null | sed 's#/.git$##' +done | rg -i '印度|jyotish|vedic|astro|yinduzhanxing|workbuddy|星轨|codex|talk' | sort +``` + +```bash +for d in /Users/wuyongnaren/Documents /Users/wuyongnaren/WorkBuddy /Users/wuyongnaren/.workbuddy /Users/wuyongnaren/Downloads /Users/wuyongnaren/Desktop /Users/wuyongnaren/.codex/attachments; do + [ -d "$d" ] && find "$d" -maxdepth 7 -type f \( -iname '*jyotish*' -o -iname '*vedic*' -o -iname '*jhora*' -o -iname '*shadbala*' -o -iname '*ashtakoot*' -o -iname '*印度占星*' -o -iname '*yinduzhanxing*' \) 2>/dev/null +done +``` diff --git a/docs/research/real_case_batch1_runtime_retrieval_audit_2026_07_03.md b/docs/research/real_case_batch1_runtime_retrieval_audit_2026_07_03.md new file mode 100644 index 00000000..00692d3f --- /dev/null +++ b/docs/research/real_case_batch1_runtime_retrieval_audit_2026_07_03.md @@ -0,0 +1,35 @@ +# Real Case Batch1 Runtime Retrieval Audit 2026-07-03 + +## Scope + +Promote already-classified `real_case_studies_batch1` from queue-only visibility into a callable local retrieval layer without treating it as primary truth. + +## Runtime Wiring + +`mcp_server._existing_interpretation_source_pack()` now exposes: + +- `real_case_calibration_layer.status = queued` +- `real_case_calibration_layer.index_status = available` +- `real_case_calibration_layer.batch_id = real_case_studies_batch1` +- domain buckets: `career`, `finance`, `relationship`, `health`, `rectification`, `timing` +- source refs from `references/real_case_studies` and `docs/benchmark` + +`real_case_calibration.status` remains `blocked` because matching-case attachment and external MEVG calibration are still required before confidence can be lifted. + +## Cleanup Boundary + +Duplicate / obsolete / quarantine files were not physically deleted in this patch. They remain hard-excluded from runtime truth by the inventory gate and source-pack tests. This avoids deleting local research history while preventing contamination of the interpretation chain. + +## Verification + +Passed: + +```bash +python3 -m pytest -q \ + tests/test_interpretation_source_inventory_gate.py \ + tests/test_interpretation_source_advanced_pipeline_contract.py \ + tests/test_interpretation_source_next_phase_contract.py \ + tests/test_mcp_strict_workflow_career.py \ + tests/test_mcp_strict_workflow_finance.py \ + tests/test_mcp_strict_workflow_relationship.py +``` diff --git a/docs/research/whole_machine_fragment_sweep_2026_07_05.md b/docs/research/whole_machine_fragment_sweep_2026_07_05.md new file mode 100644 index 00000000..74863210 --- /dev/null +++ b/docs/research/whole_machine_fragment_sweep_2026_07_05.md @@ -0,0 +1,79 @@ +# Whole-Machine Jyotish Fragment Sweep 2026-07-05 + +## Scope + +Read-only sweep for project source, local mirrors, Codex work folders, WorkBuddy copies, downloads, desktop artifacts, and configured cloud git remote. No files were deleted or moved. + +## Git State + +Current workspace: + +- Path: `/Users/wuyongnaren/Documents/印度占星` +- Branch: `codex/release-hygiene-ci` +- Upstream: `origin/codex/release-hygiene-ci` +- Remote fetch: `git@github.com:732642856/yinduzhanxing.git` +- Remote push: `https://github.com/732642856/yinduzhanxing.git` +- Worktree: dirty; do not reset or overwrite user changes. + +Remote checks: + +- `git ls-remote --heads --tags origin` failed with `ssh: connect to host github.com port 22: Operation timed out`. +- `git ls-remote --heads --tags https://github.com/732642856/yinduzhanxing.git` failed with `LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443`. +- `python3 scripts/remote_repo_visibility_check.py` now records this state structurally and sets `must_not_claim_synced: true` unless terminal refs are verified. +- `python3 scripts/remote_repo_visibility_check.py --timeout 8` returned `status: blocked`; SSH timed out, HTTPS git returned `SSL_ERROR_SYSCALL`, and GitHub API returned `UNEXPECTED_EOF_WHILE_READING`. +- `python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` later returned `status: pass` with `must_not_claim_synced: false`; network was observed to fluctuate, so this proves terminal visibility for that run, not that local dirty work was pushed. +- Browser/Web access to `https://github.com/732642856/yinduzhanxing` succeeded. +- Browser/Web branch page shows repository is public, default `main` updated Jun 13, 2026, and active `codex/release-hygiene-ci` updated Jul 2, 2026. +- Browser/Web commit page for `main` shows latest visible commit `4ff6248 Complete v6.9.14 precision modules` on Jun 13, 2026. + +Conclusion: cloud repo exists and is browser-readable, but terminal ref parity is `blocked` for this sweep. + +## Local Git Repositories Found + +Relevant candidates from bounded split scan: + +- `/Users/wuyongnaren/Documents/印度占星` — current main workspace; only implementation target. +- `/Users/wuyongnaren/.workbuddy/skills/jyotish-vedic-astrology` — dirty historical/distribution mirror; read-only reference. +- `/Users/wuyongnaren/.workbuddy/skills` — WorkBuddy skills container. +- `/Users/wuyongnaren/Documents/星轨talk` — adjacent talk engine repo with Jyotish adapters. +- `/Users/wuyongnaren/Documents/Codex/2026-06-20/732642856-talk-https-github-com-732642856/work/talk-active` — older Codex talk worktree. +- `/Users/wuyongnaren/Documents/Codex/2026-06-20/732642856-talk-https-github-com-732642856/work/talk` — older Codex talk worktree. +- `/Users/wuyongnaren/Documents/Codex/2026-06-18/new-chat-4/work/starcanvas` — adjacent astrology/canvas project. +- `/Users/wuyongnaren/Documents/Codex/2026-06-18/new-chat-4/work/starcanvas-active` — adjacent astrology/canvas project. +- `/Users/wuyongnaren/Documents/星轨画布` — adjacent astrology/canvas project. +- `/Users/wuyongnaren/WorkBuddy` and dated WorkBuddy folders — historical/reference material. + +## High-Value Fragment Files + +Keep these in mind before adapter/oracle/skill work: + +- `/Users/wuyongnaren/Documents/星轨talk/engines-repo/jyotish/jyotish-adapter.js` +- `/Users/wuyongnaren/Documents/星轨talk/engines-repo/jyotish/vedic-calc-runner.py` +- `/Users/wuyongnaren/Documents/星轨talk/engines-repo/jyotish/jyotishganit-adapter.js` +- `/Users/wuyongnaren/Documents/星轨talk/engines-repo/jyotish/jyotishganit-runner.py` +- `/Users/wuyongnaren/Documents/星轨talk/engines-repo/local-jyotish-reference-audit.js` +- `/Users/wuyongnaren/Documents/星轨talk/reports/local-jyotish-reference-audit.md` +- `/Users/wuyongnaren/Documents/ObsidianVault/03_研究_术数占星/印度占星 Jyotish.md` +- `/Users/wuyongnaren/Documents/ObsidianVault/03_研究_术数占星/印度占星研究结论 v3.md` +- `/Users/wuyongnaren/Documents/ObsidianVault/03_研究_术数占星/一楠 · 印度占星完整解盘报告 v2.md` +- `/Users/wuyongnaren/Documents/Codex/2026-06-20/732642856-yinduzhanxing-https-github-com-732642856/outputs/yinduzhanxing_local_audit_2026-06-20.md` + +## Boundaries + +- Main repo remains the runtime source of truth. +- `.workbuddy` is historical/distribution reference only. +- Obsidian, Downloads, Desktop, and personal reports may contain private material; do not commit raw contents. +- Adjacent repos may inform adapter design but are not runtime dependencies. +- Cloud-git parity remains blocked until terminal `git ls-remote` or equivalent succeeds. + +## Acceptance Commands + +```bash +python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45 +python3 -m pytest -q tests/test_runtime_import_boundaries.py tests/test_project_fragment_governance.py tests/test_preflight_fragment_scan.py tests/test_remote_repo_visibility_check.py tests/test_pre_work_check.py +``` + +```bash +git status --short --branch +git remote -v +``` diff --git a/docs/superpowers/plans/2026-07-03-local-codex-plugin-wrapper.md b/docs/superpowers/plans/2026-07-03-local-codex-plugin-wrapper.md new file mode 100644 index 00000000..c8c455ad --- /dev/null +++ b/docs/superpowers/plans/2026-07-03-local-codex-plugin-wrapper.md @@ -0,0 +1,42 @@ +# Local Codex Plugin Wrapper Implementation Plan +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +**Goal:** Add a minimal local Codex plugin wrapper around the existing Jyotish repo so Codex can install it as a plugin using the repo's current skills and MCP server. +**Architecture:** Keep the repo as source of truth. Add only a `.codex-plugin/plugin.json` manifest that points at existing `skills/` and the existing root `mcp_server.py`. Lock it with one static test and document local install/test steps. +**Tech Stack:** JSON manifest, existing Python MCP server, pytest static checks, Codex local plugin install flow. +## Global Constraints +- Reuse existing `SKILL.md`, `skills/`, and `mcp_server.py`; no duplicate runtime chain. +- Keep plugin scope local-only; no marketplace publishing work. +- Smallest diff wins: one manifest, one test, one short install doc update. + +### Task 1: Add plugin manifest coverage +**Files:** +- Modify: `tests/test_runtime_import_boundaries.py` or create focused plugin manifest test if cleaner +- Test: `tests/test_codex_plugin_wrapper.py` +**Interfaces:** +- Consumes: `.codex-plugin/plugin.json` +- Produces: static validation that plugin manifest exists and points at current repo assets +- [ ] **Step 1: Write failing static test** +- [ ] **Step 2: Run test to confirm failure** +- [ ] **Step 3: Add minimal manifest or adjust expectations** +- [ ] **Step 4: Re-run targeted test** + +### Task 2: Add minimal local plugin manifest +**Files:** +- Create: `.codex-plugin/plugin.json` +**Interfaces:** +- Consumes: existing `skills/`, root `mcp_server.py` +- Produces: installable local plugin metadata +- [ ] **Step 1: Use plugin spec sample to choose minimal accepted fields** +- [ ] **Step 2: Point `skills` at existing `./skills/`** +- [ ] **Step 3: Inline `mcpServers` config for `mcp_server.py`** +- [ ] **Step 4: Validate manifest with plugin validator** + +### Task 3: Document local install/test flow +**Files:** +- Modify: `README.md` +**Interfaces:** +- Consumes: local plugin install command shape +- Produces: copy-paste local install, reinstall, and test commands +- [ ] **Step 1: Add minimal plugin install section** +- [ ] **Step 2: Include reinstall/new-thread note** +- [ ] **Step 3: Run targeted doc/static checks** diff --git a/docs/superpowers/plans/2026-07-03-open-source-p0-file-execution-plan.md b/docs/superpowers/plans/2026-07-03-open-source-p0-file-execution-plan.md new file mode 100644 index 00000000..084cc324 --- /dev/null +++ b/docs/superpowers/plans/2026-07-03-open-source-p0-file-execution-plan.md @@ -0,0 +1,273 @@ +# Open Source P0 File Execution Plan +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +**Goal:** Finish the three P0 open-source integration fronts with the smallest real diff: `panchanga_api` sidecar integration, VedAstro official-default closure, and PyJHora black-box evidence consumption. +**Architecture:** Reuse the existing unified planner, API server, MCP strict workflow, local `muhurta/prashna/remedies` modules, and existing VedAstro/PyJHora audit scripts. Do not add a parallel runtime chain. Extend existing payloads so web, MCP, and skill all read the same contract. +**Tech Stack:** Python 3.11 stdlib, existing `scripts/` modules, existing `jyotish-app` frontend, pytest, existing repo docs. + +## Global Constraints +- Reuse existing repo modules; no new subsystem. +- Keep authority order: VedAstro official snapshot first, local supplemental modules second, local fallback last. +- `PyJHora` remains black-box evidence only; do not import/copy AGPL code. +- `panchanga_api` ideas may shape sidecar outputs, but local computation stays in `scripts/muhurta.py`, `scripts/prashna.py`, `scripts/remedies.py`. +- Keep diffs tight: prefer helper functions inside current files over new files. +- Every task ends with targeted tests, not hand-waving. + +## File Map + +### P0-A: `panchanga_api`-style sidecar integration + +| File | Role | +|---|---| +| `scripts/unified_consultation_orchestrator.py` | Decide when workflow should run `prashna`, `muhurta/panchanga`, and audited remedies sidecars. | +| `scripts/jyotish_api_server.py` | Build/attach `prashna`, `muhurta_panchanga`, and `audited_remedies` packets in one workflow response. | +| `scripts/muhurta.py` | Existing local Panchanga/Muhurta engine. Expose one compact helper fit for API/strict workflow payloads. | +| `scripts/prashna.py` | Existing time-question engine. Reuse, do not rebuild. | +| `scripts/remedies.py` | Existing remedies engine; only fed audited gate outputs. | +| `mcp_server.py` | Surface same sidecars in strict workflow output. | +| `jyotish-app/index.html` | Existing three-entry UI shell. Only minor control copy/visibility changes if needed. | +| `jyotish-app/main.js` | Send timing-sidecar inputs and render sidecar outputs. | +| `jyotish-app/renderers.js` | Reuse existing Panchanga rendering slot instead of new UI surface. | +| `tests/test_unified_consultation_orchestrator.py` | Planner contract tests. | +| `tests/test_api_server_security.py` | Workflow contract tests. | +| `tests/test_muhurta.py` | Compact Muhurta helper tests. | +| `tests/test_frontend_productization.py` | Frontend contract/render tests. | + +### P0-B: VedAstro official-default closure + +| File | Role | +|---|---| +| `scripts/vedastro_service_adapter.py` | Single official network boundary. Cache/queue/free-tier behavior lives here first. | +| `scripts/vedastro_priority.py` | Shared official-first source-priority contract. | +| `scripts/diagnose_vedastro_mode.py` | Runtime diagnosis for `official_extended` vs fallback, free-tier eligibility, queue/cache state. | +| `scripts/jyotish_api_server.py` | Attach official summary/runtime truth to chart/full-reading/thematic/workflow responses. | +| `scripts/unified_consultation_orchestrator.py` | Planner-visible official-step intent only; no extra engine. | +| `mcp_server.py` | Surface same runtime truth and official execution layer summary. | +| `jyotish-app/api-bridge.js` | Preserve official/runtime truth fields from API. | +| `jyotish-app/main.js` | Render compact source-priority/runtime-truth block. | +| `jyotish-app/professional-reading.js` | Show official/partial/fallback boundary in final reading. | +| `jyotish-app/ai-chat.js` | Carry official evidence snapshot/runtime truth into follow-up payload. | +| `tests/test_vedastro_runtime_mode_diagnostics.py` | Diagnose contract. | +| `tests/test_vedastro_official_full_snapshot.py` | Snapshot contract. | +| `tests/test_vedastro_service_adapter_executor.py` | Adapter execution/caching behavior. | +| `tests/test_api_server_security.py` | Response contract propagation. | +| `tests/test_frontend_productization.py` | Frontend visibility contract. | +| `tests/test_vedastro_official_mcp_bridge.py` | Official bridge smoke. | + +### P0-C: PyJHora black-box evidence consumption + +| File | Role | +|---|---| +| `scripts/generate_pyjhora_oracle_artifact_manifest.py` | Canonical artifact manifest generator. | +| `scripts/oracle_benchmark_inventory.py` | Make PyJHora artifact counts/queryable fronts visible. | +| `scripts/external_oracle_sanity_closure.py` | Summarize PyJHora evidence availability into closure report. | +| `scripts/historical_event_backtest.py` | Attach matching external evidence refs into backtest output. | +| `scripts/oracle_closure_master_dashboard.py` | Lift PyJHora evidence readiness into one master board. | +| `README.md` | State black-box boundary and what counts as closed vs not closed. | +| `tests/test_pyjhora_oracle_artifact_manifest.py` | Manifest shape tests. | +| `tests/test_oracle_benchmark_inventory.py` | Inventory tests. | +| `tests/test_external_oracle_sanity_closure.py` | Closure summary tests. | +| `tests/test_historical_event_backtest.py` | Backtest evidence-link tests. | +| `tests/test_oracle_closure_master_dashboard.py` | Master dashboard tests. | + +## Task 1: Add `muhurta_panchanga` sidecar to unified workflow +**Files:** +- Modify: `scripts/muhurta.py` +- Modify: `scripts/unified_consultation_orchestrator.py` +- Modify: `scripts/jyotish_api_server.py` +- Test: `tests/test_muhurta.py` +- Test: `tests/test_unified_consultation_orchestrator.py` +- Test: `tests/test_api_server_security.py` + +**Interfaces:** +- Consumes: existing birth/location/time payload, optional `reference_date`, optional user question/topic. +- Produces: `muhurta_panchanga: {status, source, panchanga, muhurta_windows, remedy_timing, blocked_reason?}` in workflow/module payload. + +- [ ] **Step 1: Write failing helper test in `tests/test_muhurta.py`** + - Target a new compact helper in `scripts/muhurta.py`, e.g. `build_muhurta_sidecar(...)`. +- [ ] **Step 2: Run targeted test to verify failure** + - Run: `python3 -m pytest tests/test_muhurta.py -q` +- [ ] **Step 3: Write failing planner/API tests** + - `tests/test_unified_consultation_orchestrator.py`: planner adds `run_muhurta_panchanga` for timing/remedy/prashna routes. + - `tests/test_api_server_security.py`: workflow response includes `muhurta_panchanga`. +- [ ] **Step 4: Run targeted planner/API tests** +- [ ] **Step 5: Implement minimal helper in `scripts/muhurta.py`** + - Reuse current Panchanga/Muhurta math. + - Return compact summary only; no giant raw blob. +- [ ] **Step 6: Thread helper into `scripts/jyotish_api_server.py`** + - Add one private helper, e.g. `_compute_muhurta_panchanga(...)`. + - Attach result to consultation workflow and high-rigor/full-reading payloads where timing/remedies are relevant. +- [ ] **Step 7: Update planner in `scripts/unified_consultation_orchestrator.py`** + - Add `run_muhurta_panchanga` only when route needs timing/remedy support. +- [ ] **Step 8: Re-run targeted tests** + +## Task 2: Surface timing sidecar in MCP and frontend +**Files:** +- Modify: `mcp_server.py` +- Modify: `jyotish-app/main.js` +- Modify: `jyotish-app/renderers.js` +- Modify: `jyotish-app/index.html` only if an existing slot/label is missing +- Test: `tests/test_frontend_productization.py` + +**Interfaces:** +- Consumes: workflow payload with `muhurta_panchanga`, `prashna`, `audited_remedies`. +- Produces: visible timing/remedy panel in frontend + strict workflow payload exposure in MCP. + +- [ ] **Step 1: Write failing frontend/MCP contract test** + - Assert payload/rendering can see `muhurta_panchanga`. +- [ ] **Step 2: Run targeted tests to verify failure** +- [ ] **Step 3: Thread `muhurta_panchanga` through `mcp_server.py` strict output** +- [ ] **Step 4: Reuse existing Panchanga render path in `jyotish-app/renderers.js`** + - No new tab unless current UI truly has nowhere to show it. +- [ ] **Step 5: Update `jyotish-app/main.js` to render sidecar from unified workflow response** +- [ ] **Step 6: Re-run targeted tests** + +## Task 3: Make VedAstro official path the real default contract +**Files:** +- Modify: `scripts/vedastro_service_adapter.py` +- Modify: `scripts/vedastro_priority.py` +- Modify: `scripts/diagnose_vedastro_mode.py` +- Modify: `scripts/jyotish_api_server.py` +- Test: `tests/test_vedastro_runtime_mode_diagnostics.py` +- Test: `tests/test_vedastro_service_adapter_executor.py` +- Test: `tests/test_vedastro_official_full_snapshot.py` +- Test: `tests/test_api_server_security.py` + +**Interfaces:** +- Consumes: same birth payload/reference date/theme, existing env/free-tier settings, adapter cache/queue config. +- Produces: one normalized block: + - `runtime_truth` + - `official_execution_layers` + - `source_priority` + - `free_tier_strategy` + +- [ ] **Step 1: Write/extend failing diagnostics tests** + - Missing premium key should not masquerade as universal blocker. + - Free-tier cache/queue possibility should be explicit. +- [ ] **Step 2: Run diagnostics tests to verify failure** +- [ ] **Step 3: Write/extend failing adapter/API tests** + - Repeated same request should show cache-hit semantics. + - Official snapshot status should flow as `ok/partial/blocked`, not disappear. +- [ ] **Step 4: Run targeted adapter/API tests** +- [ ] **Step 5: Tighten `scripts/vedastro_service_adapter.py`** + - Reuse existing cache + queue path. + - Expose explicit metadata, not hidden internal state. +- [ ] **Step 6: Tighten `scripts/vedastro_priority.py`** + - Keep one source-priority truth used by API/MCP/frontend. +- [ ] **Step 7: Thread normalized blocks through `scripts/jyotish_api_server.py`** + - `/api/chart` + - `/api/full-reading` + - `/api/thematic_report` + - `/api/consultation_workflow` +- [ ] **Step 8: Re-run targeted tests** + +## Task 4: Show VedAstro official/fallback truth in MCP + frontend +**Files:** +- Modify: `mcp_server.py` +- Modify: `jyotish-app/api-bridge.js` +- Modify: `jyotish-app/main.js` +- Modify: `jyotish-app/professional-reading.js` +- Modify: `jyotish-app/ai-chat.js` +- Test: `tests/test_frontend_productization.py` +- Test: `tests/test_vedastro_official_mcp_bridge.py` + +**Interfaces:** +- Consumes: API payload with `runtime_truth`, `official_execution_layers`, `source_priority`. +- Produces: user-visible compact truth block and AI follow-up payload carrying same boundary. + +- [ ] **Step 1: Write failing frontend contract tests** + - Assert frontend preserves and renders runtime-truth/source-priority data. +- [ ] **Step 2: Run targeted tests to verify failure** +- [ ] **Step 3: Thread fields via `jyotish-app/api-bridge.js`** +- [ ] **Step 4: Render concise truth block in `jyotish-app/main.js` / `professional-reading.js`** + - Show direct facts: official `ok/partial/blocked`, cache hit, queue used, fallback used or not. +- [ ] **Step 5: Pass same block into `jyotish-app/ai-chat.js` follow-up payload** +- [ ] **Step 6: Surface same summary in `mcp_server.py` strict workflow output** +- [ ] **Step 7: Re-run targeted tests** + +## Task 5: Normalize PyJHora artifact manifest for downstream consumers +**Files:** +- Modify: `scripts/generate_pyjhora_oracle_artifact_manifest.py` +- Modify: `scripts/oracle_benchmark_inventory.py` +- Modify: `scripts/external_oracle_sanity_closure.py` +- Test: `tests/test_pyjhora_oracle_artifact_manifest.py` +- Test: `tests/test_oracle_benchmark_inventory.py` +- Test: `tests/test_external_oracle_sanity_closure.py` + +**Interfaces:** +- Consumes: `references/oracle/artifacts/pyjhora_*` + `pending_packets/*pyjhora*.json`. +- Produces: consistent per-front summary (`dasha`, `shadbala`, `tajika_sahams`) for audit scripts. + +- [ ] **Step 1: Write failing manifest/inventory tests** + - Need front-wise counts + file refs + boundary text. +- [ ] **Step 2: Run targeted tests to verify failure** +- [ ] **Step 3: Extend `generate_pyjhora_oracle_artifact_manifest.py` minimally** + - Add any missing fields needed by inventory/closure consumers. +- [ ] **Step 4: Update `oracle_benchmark_inventory.py` to consume manifest, not ad hoc file scanning** +- [ ] **Step 5: Update `external_oracle_sanity_closure.py` to report PyJHora evidence availability explicitly** +- [ ] **Step 6: Re-run targeted tests** + +## Task 6: Feed PyJHora evidence into backtest + master dashboard +**Files:** +- Modify: `scripts/historical_event_backtest.py` +- Modify: `scripts/oracle_closure_master_dashboard.py` +- Modify: `README.md` +- Test: `tests/test_historical_event_backtest.py` +- Test: `tests/test_oracle_closure_master_dashboard.py` + +**Interfaces:** +- Consumes: normalized PyJHora manifest/inventory summary. +- Produces: + - backtest result includes matching `external_evidence_refs` + - master dashboard shows PyJHora evidence readiness + - README truth text stays honest + +- [ ] **Step 1: Write failing backtest/dashboard tests** +- [ ] **Step 2: Run targeted tests to verify failure** +- [ ] **Step 3: Update `historical_event_backtest.py`** + - Link relevant artifact refs by front/domain; do not overclaim validation. +- [ ] **Step 4: Update `oracle_closure_master_dashboard.py`** + - Lift manifest summary into one closure board. +- [ ] **Step 5: Refresh README wording** + - Keep “black-box evidence only” explicit. +- [ ] **Step 6: Re-run targeted tests** + +## Task 7: Final regression pack +**Files:** +- No new files unless a missing narrow test is unavoidable + +**Run:** +- [ ] `python3 -m pytest tests/test_muhurta.py tests/test_unified_consultation_orchestrator.py tests/test_api_server_security.py -q` +- [ ] `python3 -m pytest tests/test_vedastro_runtime_mode_diagnostics.py tests/test_vedastro_service_adapter_executor.py tests/test_vedastro_official_full_snapshot.py tests/test_vedastro_official_mcp_bridge.py tests/test_frontend_productization.py -q` +- [ ] `python3 -m pytest tests/test_pyjhora_oracle_artifact_manifest.py tests/test_oracle_benchmark_inventory.py tests/test_external_oracle_sanity_closure.py tests/test_historical_event_backtest.py tests/test_oracle_closure_master_dashboard.py -q` +- [ ] Run one manual local workflow smoke: + - direct chart + - prashna/time question + - one repeated request to verify cache hit metadata +- [ ] Record remaining blocked items explicitly, especially: + - official live endpoint availability + - free-tier queue delay + - missing PyJHora evidence fronts + +## Fastest Execution Order + +1. Task 3 +2. Task 4 +3. Task 1 +4. Task 2 +5. Task 5 +6. Task 6 +7. Task 7 + +Reason: + +- VedAstro default truth is the highest-value user-facing fix. +- `muhurta/panchanga` sidecar reuses local code and is cheap once workflow contract is stable. +- PyJHora evidence work is mainly audit/dashboards, not user runtime critical path. + +## Stop Conditions + +Stop and mark `blocked` if any of these happen: + +- VedAstro official endpoint can only return `blocked` in the current environment and no cache sample exists. +- A proposed PyJHora consumption change would require importing AGPL code instead of consuming stored artifacts. +- `panchanga_api`-style sidecar would require a second planner/entry system instead of extending current unified workflow. diff --git a/docs/superpowers/plans/2026-07-03-prashna-remedies-unified-entry.md b/docs/superpowers/plans/2026-07-03-prashna-remedies-unified-entry.md new file mode 100644 index 00000000..818d99d3 --- /dev/null +++ b/docs/superpowers/plans/2026-07-03-prashna-remedies-unified-entry.md @@ -0,0 +1,84 @@ +# Prashna Remedies Unified Entry Implementation Plan +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +**Goal:** Reuse existing `prashna` and `remedies` modules inside the unified consultation workflow so web/API/skill can expose three parallel entry modes: direct chart, rectification, and time-question (Prashna). +**Architecture:** Keep the current `execute_consultation_workflow()` and `UnifiedConsultationOrchestrator` as the single runtime planner. Add one new `entry_mode` (`prashna`) and one gate-fed remedies path that consumes `strict_audit_gate`/guided-topic conclusion packets instead of raw free-form frontend fields. Frontend only switches entry payload shape and display mode; backend remains the source of truth. +**Tech Stack:** Python stdlib, existing repo modules (`prashna.py`, `remedies.py`, `unified_consultation_orchestrator.py`, `jyotish_api_server.py`), existing web app JS/HTML, pytest. + +## Global Constraints +- Reuse existing modules; no new subsystem. +- Keep source priority: VedAstro official snapshot first, local supplemental second, fallback last. +- Remedies must not accept arbitrary raw advice inputs from frontend once unified path exists. +- Frontend must expose exactly three peer entry modes: `direct_chart`, `rectification`, `prashna`. +- Keep diffs small and local to existing orchestrator/API/frontend files. + +### Task 1: Add `prashna` to unified runtime planner +**Files:** +- Modify: `scripts/unified_consultation_orchestrator.py` +- Test: `tests/test_unified_consultation_orchestrator.py` +**Interfaces:** +- Consumes: `entry_mode: str`, `question: str | None`, existing route/theme normalization. +- Produces: runtime planner contract supporting `entry_mode == "prashna"` and route/theme selection for Prashna. +- [ ] **Step 1: Write failing tests** +- [ ] **Step 2: Run planner tests to verify failure** +- [ ] **Step 3: Implement minimal planner support for `prashna`** +- [ ] **Step 4: Re-run planner tests** + +### Task 2: Route consultation workflow through Prashna entry mode +**Files:** +- Modify: `scripts/jyotish_api_server.py` +- Test: `tests/test_api_server_security.py` +**Interfaces:** +- Consumes: `POST /api/consultation_workflow` payload w/ `entry_mode = "prashna"`, `question`, `question_text`, optional `horary_number`, existing birth/location/time fields. +- Produces: consultation workflow result containing `entry_mode`, `runtime_planner`, `prashna` result block, and shared provenance fields. +- [ ] **Step 1: Write failing API workflow tests for `prashna` entry** +- [ ] **Step 2: Run targeted tests to verify failure** +- [ ] **Step 3: Implement minimal `prashna` entry path by reusing `_compute_prashna()`** +- [ ] **Step 4: Re-run targeted tests** + +### Task 3: Make unified remedies consume strict gate output only +**Files:** +- Modify: `scripts/jyotish_api_server.py` +- Test: `tests/test_api_server_security.py` +**Interfaces:** +- Consumes: guided-topic / strict workflow conclusion packet containing `strict_audit_gate`, `topic`, `supporting_planets`, `active_dasha_lord`, and strength inputs already computed in workflow. +- Produces: remedies response derived from audited conclusion context, not arbitrary frontend-entered `shadbala`/`doshas` blobs in unified mode. +- [ ] **Step 1: Write failing test showing unified remedies path rejects missing gate context** +- [ ] **Step 2: Run targeted test to verify failure** +- [ ] **Step 3: Implement minimal audited remedies adapter around existing `recommend_remedies()`** +- [ ] **Step 4: Re-run targeted tests** + +### Task 4: Expose three entry modes in frontend +**Files:** +- Modify: `jyotish-app/index.html` +- Modify: `jyotish-app/main.js` +- Modify: `jyotish-app/api-bridge.js` +- Test: `tests/test_frontend_productization.py` +**Interfaces:** +- Consumes: user choice among `direct_chart`, `rectification`, `prashna`. +- Produces: frontend payloads to `/api/consultation_workflow`, plus Prashna-specific question fields when `prashna` selected. +- [ ] **Step 1: Write failing frontend contract test for 3 entry modes** +- [ ] **Step 2: Run targeted frontend test to verify failure** +- [ ] **Step 3: Implement minimal UI/payload switch reuse of existing sections** +- [ ] **Step 4: Re-run targeted frontend tests** + +### Task 5: Wire remedies display to unified audited output +**Files:** +- Modify: `jyotish-app/main.js` +- Test: `tests/test_frontend_productization.py` +**Interfaces:** +- Consumes: unified workflow response including audited remedies packet. +- Produces: remedies section rendered from audited packet, not standalone free-form request flow. +- [ ] **Step 1: Write failing render test** +- [ ] **Step 2: Run targeted test to verify failure** +- [ ] **Step 3: Implement minimal audited remedies rendering** +- [ ] **Step 4: Re-run targeted tests** + +### Task 6: End-to-end verification +**Files:** +- Modify if needed: `tests/test_api_server_security.py`, `tests/test_frontend_productization.py` +**Interfaces:** +- Produces: passing targeted regression coverage for planner, API workflow, remedies gate, and frontend entry modes. +- [ ] **Step 1: Run targeted backend tests** +- [ ] **Step 2: Run targeted frontend tests** +- [ ] **Step 3: Run one live local consultation/prashna smoke probe** +- [ ] **Step 4: Record any remaining boundary explicitly** diff --git a/docs/superpowers/plans/2026-07-03-vedastro-runtime-truth-contract.md b/docs/superpowers/plans/2026-07-03-vedastro-runtime-truth-contract.md new file mode 100644 index 00000000..2df1f881 --- /dev/null +++ b/docs/superpowers/plans/2026-07-03-vedastro-runtime-truth-contract.md @@ -0,0 +1,105 @@ +# VedAstro Runtime Truth Contract Implementation Plan +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +**Goal:** Expose machine-checkable runtime truth for VedAstro official execution and interpretation-source invocation so users and APIs can see what actually ran. +**Architecture:** Extend the existing unified consultation/high-rigor contract instead of building a new pipeline. Reuse `vedastro_service_adapter`, `vedastro_priority`, existing interpretation source inventory/strict workflow markers, and API response contracts. +**Tech Stack:** Python 3.11, existing `scripts/` API server/orchestrator modules, pytest. +## Global Constraints +- Reuse existing repo modules; no new parallel runtime chain. +- Keep VedAstro official as preferred evidence, but do not overclaim full-catalog execution. +- Add tests first for new response contract fields. +- Preserve current honesty boundaries and blocked states. + +### Task 1: Add failing API contract tests for runtime truth fields +**Files:** +- Modify: `tests/test_vedastro_external_technique_evidence.py` +- Modify: `tests/test_api_server_security.py` +**Interfaces:** +- Consumes: `JyotishAPIHandler._high_rigor_vedastro_official_summary`, `execute_consultation_workflow` +- Produces: expected fields `runtime_truth`, `official_execution_layers`, `interpretation_source_runtime_coverage` +- [ ] Step 1: Write failing tests for consultation/high-rigor responses. +- [ ] Step 2: Run targeted pytest to confirm failure. +- [ ] Step 3: Keep assertions narrow: presence, semantics, blocked/partial handling. +- [ ] Step 4: Re-run targeted pytest. + +### Task 2: Implement VedAstro runtime truth summary +**Files:** +- Modify: `scripts/jyotish_api_server.py` +- Check: `scripts/vedastro_priority.py` +**Interfaces:** +- Consumes: chart modules, `vedastro_official_full_snapshot`, `source_priority`, evidence snapshot +- Produces: normalized runtime truth block with official mode, readiness, executed layers, blocked reasons, catalog-vs-runtime boundary +- [ ] Step 1: Add helper(s) building normalized `runtime_truth` summary. +- [ ] Step 2: Thread summary into `_high_rigor_vedastro_official_summary`. +- [ ] Step 3: Thread same summary into consultation workflow top-level payload. +- [ ] Step 4: Run targeted tests. + +### Task 3: Implement interpretation-source runtime coverage summary +**Files:** +- Modify: `scripts/jyotish_api_server.py` +- Check: `mcp_server.py`, `scripts/interpretation_source_inventory_gate.py` +**Interfaces:** +- Consumes: strict workflow secondary-context markers, existing source-pack status, current audit boundaries +- Produces: API-visible summary of proven runtime visibility vs unproven source assets +- [ ] Step 1: Add helper returning compact coverage summary. +- [ ] Step 2: Attach to consultation workflow and thematic/high-rigor payloads. +- [ ] Step 3: Ensure summary distinguishes `proven_runtime_markers` from `not_fully_closed`. +- [ ] Step 4: Run targeted tests. + +### Task 4: Add reusable audit script for machine-checkable coverage +**Files:** +- Create: `scripts/interpretation_source_runtime_coverage.py` +- Add Test: `tests/test_interpretation_source_runtime_coverage.py` +**Interfaces:** +- Consumes: existing inventory gate data + strict workflow markers from runtime packs +- Produces: JSON/markdown report with extraction/source-pack/runtime visibility gaps +- [ ] Step 1: Write failing test for minimal report shape. +- [ ] Step 2: Implement minimal script reusing existing modules. +- [ ] Step 3: Run targeted tests. + +### Task 5: Verify end-to-end targeted regression +**Files:** +- None or docs if output contract docs need refresh +**Interfaces:** +- Consumes: new contract fields/tests/scripts +- Produces: fresh verification evidence +- [ ] Step 1: Run targeted pytest set covering API contract + new coverage tool. +- [ ] Step 2: Run `python3 scripts/diagnose_vedastro_mode.py`. +- [ ] Step 3: Run new coverage script once in JSON mode. +- [ ] Step 4: Summarize actual status, not aspirational status. + + +### Task 6: Surface runtime truth in MCP and frontend result payloads +**Files:** +- Modify: `mcp_server.py` +- Modify: `jyotish-app/skill-map.js` +- Test: `tests/test_frontend_productization.py` and/or existing MCP/API contract tests +**Interfaces:** +- Consumes: `consultation_workflow` response contract +- Produces: visible runtime truth / source coverage status in MCP strict workflow output and frontend result panels +- [ ] Step 1: Add failing tests for visible payload fields. +- [ ] Step 2: Thread fields through MCP strict workflow response. +- [ ] Step 3: Render compact runtime truth/source coverage block in frontend result page. +- [ ] Step 4: Run targeted tests. + +### Task 7: Harden official-extended runtime preflight +**Files:** +- Modify: `scripts/diagnose_vedastro_mode.py` +- Modify: `scripts/vedastro_service_adapter.py` and/or `.env.official.example` only if needed +- Test: existing VedAstro diagnostics/adapter tests +**Interfaces:** +- Consumes: current env + adapter time budget +- Produces: sharper preflight diagnosis for why official mode is not ready; no silent fast fallback confusion +- [ ] Step 1: Add failing diagnostics tests if missing. +- [ ] Step 2: Expose concrete readiness blockers in output contract. +- [ ] Step 3: Re-run diagnostics tests. + +### Task 8: Split slow verification into smaller quality-gate profile(s) +**Files:** +- Modify: `scripts/run_quality_gate.py` +- Modify: relevant tests/docs if profile list changes +**Interfaces:** +- Consumes: current gate profiles and long-running test groups +- Produces: smaller verification profile(s) for runtime-truth/strict-workflow/official contracts +- [ ] Step 1: Add failing tests or assertions for new profile names/commands. +- [ ] Step 2: Implement profile split and command grouping. +- [ ] Step 3: Run targeted gate/profile tests. diff --git a/docs/superpowers/plans/2026-07-04-bv-raman-report-book-structure.md b/docs/superpowers/plans/2026-07-04-bv-raman-report-book-structure.md new file mode 100644 index 00000000..73a530e0 --- /dev/null +++ b/docs/superpowers/plans/2026-07-04-bv-raman-report-book-structure.md @@ -0,0 +1,68 @@ +# B.V. Raman Long Report Book Structure Implementation Plan +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +**Goal:** Convert the existing single-file comprehensive chart study into a reusable long-report book structure that can serve Markdown-first authoring and later PDF export. +**Architecture:** Keep the existing [`/Users/wuyongnaren/Documents/印度占星/docs/reports/bv_raman_style_comprehensive_chart_research_REDACTED_DATE_REDACTED_TIME_v1.md`](/Users/wuyongnaren/Documents/印度占星/docs/reports/bv_raman_style_comprehensive_chart_research_REDACTED_DATE_REDACTED_TIME_v1.md) as the source draft, then add a new `chart_research_REDACTED_DATE_REDACTED_TIME/` directory with `book.md` as the main concatenation entry, chapter files for thematic expansion, appendix files for raw evidence, and a small metadata file for later export tooling. No new PDF pipeline is added now because none is already wired in this repo. +**Tech Stack:** Markdown, YAML metadata, existing repo docs conventions, local JSON artifacts under `.tmp/chart_study`. +## Global Constraints +- Reuse existing report content; do not rewrite the full report from scratch. +- Keep Markdown as the source of truth; PDF remains an export artifact, not a second authoring format. +- Do not add new dependencies or a speculative PDF toolchain. +- Preserve honesty boundaries about VedAstro/PyJHora/jyotishganit closure. +- Prefer links to existing JSON artifacts over copying large raw blobs into prose chapters. +- Before editing chapters, read `docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/README.md` and `docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/appendices/H_error_ledger_and_preflight.md`. + +### Task 1: Add the long-report container +**Files:** +- Create: `/Users/wuyongnaren/Documents/印度占星/docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/README.md` +- Create: `/Users/wuyongnaren/Documents/印度占星/docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/book.md` +- Create: `/Users/wuyongnaren/Documents/印度占星/docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/metadata.yaml` +**Interfaces:** +- Consumes: existing v1 report and `.tmp/chart_study/*.json` +- Produces: one stable report root that later tools or humans can extend +- [ ] **Step 1: Create report root directory structure** +- [ ] **Step 2: Add `README.md` explaining source-of-truth and export intent** +- [ ] **Step 3: Add `metadata.yaml` for title/author/date/export hints** +- [ ] **Step 4: Add `book.md` listing chapters and appendices in final reading order** + +### Task 2: Add chapter skeletons mapped to the requested methodology +**Files:** +- Create: `/Users/wuyongnaren/Documents/印度占星/docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/chapters/*.md` +**Interfaces:** +- Consumes: user-requested B.V. Raman / Parāśara / Jaimini scope +- Produces: one file per chapter so later expansion stays bounded +- [ ] **Step 1: Create 00–21 chapter files** +- [ ] **Step 2: Put a short contract at top of each chapter saying what belongs there** +- [ ] **Step 3: Pre-fill only the first few chapters with live links to existing content/data** +- [ ] **Step 4: Leave later chapters as clean skeletons instead of fake filled content** + +### Task 3: Add appendix and data references +**Files:** +- Create: `/Users/wuyongnaren/Documents/印度占星/docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/appendices/*.md` +- Populate/Reference: `/Users/wuyongnaren/Documents/印度占星/docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/data/` +**Interfaces:** +- Consumes: `.tmp/chart_study/*.json` +- Produces: report appendix map and raw evidence pointers +- [ ] **Step 1: Create appendix stubs for raw tables, dasha boundaries, varga positions, parity, audit, glossary** +- [ ] **Step 2: Reference current `.tmp/chart_study` artifacts instead of duplicating them** +- [ ] **Step 3: Explain which appendices are ready vs blocked** + +### Task 4: Connect old draft into new main chain +**Files:** +- Modify/Create: `/Users/wuyongnaren/Documents/印度占星/docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME/README.md` +- Keep: `/Users/wuyongnaren/Documents/印度占星/docs/reports/bv_raman_style_comprehensive_chart_research_REDACTED_DATE_REDACTED_TIME_v1.md` +**Interfaces:** +- Consumes: existing v1 draft headings and current chapter structure +- Produces: migration notes from monolith draft to book structure +- [ ] **Step 1: Mark the old v1 report as the seed draft** +- [ ] **Step 2: Map v1 sections to new chapter files** +- [ ] **Step 3: Keep migration incremental instead of copying all prose immediately** + +### Task 5: Verify structure only +**Files:** +- Verify: report directory tree and links +**Interfaces:** +- Consumes: created Markdown files +- Produces: a structure that humans can open immediately +- [ ] **Step 1: List created files** +- [ ] **Step 2: Open `book.md` and `README.md` to confirm links** +- [ ] **Step 3: Avoid claiming PDF export exists until an actual exporter is wired and tested** diff --git a/docs/vibe_coding_setup.md b/docs/vibe_coding_setup.md new file mode 100644 index 00000000..c5686cce --- /dev/null +++ b/docs/vibe_coding_setup.md @@ -0,0 +1,26 @@ +# Vibe Coding Setup + +Use Cline as the main AI coding surface for this repo. Use Aider for small low-cost code edits. Use Dyad only for fast front-end prototypes such as 星轨talk pages. + +## Cline MCP + +Generate config: + +```bash +python3 scripts/print_cline_mcp_config.py +``` + +Install project-local config: + +```bash +python3 scripts/print_cline_mcp_config.py --install-project +``` + +This writes `.cline/mcp.json` with the current checkout path. `.cline/` is local-only and ignored by git. + +## Recommended Split + +- Cline: main repo work, MCP tools, strict workflow checks. +- Aider: precise low-cost edits with git diffs. +- Dyad: quick UI prototypes, not the main Jyotish runtime. + diff --git a/findings.md b/findings.md index 6ef21450..77f11e64 100644 --- a/findings.md +++ b/findings.md @@ -213,3 +213,23 @@ - `python3 scripts/audit_capabilities.py --mode validate` 通过,显示 `technique_count=89`、`problem_count=0`;`python3 scripts/audit_fragments.py --strict` 通过,显示当前仓 `candidate_count=0`、`untracked_count=0`。 - 云端 HTTPS refs 已确认:本地 `codex/release-hygiene-ci@767a5c6` 与远端 `refs/heads/codex/release-hygiene-ci@767a5c6` 对齐。 - 根因不是“项目没有资料”,而是现有测试多守文档/注册表存在性,没有守 `mcp_server.py::_collect_strict_evidence`、AI prompt pack 和用户可见 strict contract 必须显式携带这些资料层。下一步应只补显式调用链与测试,不重写规则体系。 + +## 2026-07-02 6月20日后算力消耗升高排查结论 + +- Codex 本地 `session_index.jsonl` 显示 2026-06-20 开始新增/更新 9 个线程,包括 `开发 flomo App 并上架 App Store`、`优化印度占星项目`、`继续优化星轨talk项目`、`梳理 StarCanvas 进度`、`梳理印度占星项目进度` 等;这不是印度占星单项目单点故障,而是多项目长线程同时启动。 +- `.codex/archived_sessions` 中 2026-06-21/22 出现多个 100MB 级长会话:`019eed29...` 约 127MB、`019ee916...` 约 139MB、`019eef04...` 约 58MB。会话统计显示大量 `exec_command` / `apply_patch` / `write_stdin`,并反复产生 `compacted` 记录,说明工具输出和压缩上下文被不断带入模型请求。 +- archived session 的 `token_count` 元数据按 last usage 聚合:2026-06-21 约 124.6M total tokens,2026-06-22 约 556.6M,2026-06-23 约 580.5M,2026-06-24 约 366.3M,2026-06-28 约 322.6M。最大线程 `019eed29...` 约 821.6M total tokens,其中大部分为 cached input,但仍会造成显著算力/上下文消耗。 +- 直接触发 token 放大的模式是“大范围读取 + 长输出 + 长线程自动压缩”:例如同一回合并行 `sed -n` 读取 README/SKILL/index/API server/多份 reference,每个命令允许 8k-22k 输出 token;后续继续在同一线程中工作,使 cached input 和 compacted 摘要持续变大。 +- 代码仓层面,2026-06-21 commit `11bdee3` 把 CI/质量门从轻量 Python 检查升级为安装 Node/npm、`npm ci`、全 pytest、quick quality gate、前端 build、Python package build;后续 `scripts/run_quality_gate.py` 又加入 runtime smoke、browser/release profile、artifact 诊断、manifest gate、oracle gate。这解释了本地/云端 CPU 与 CI 时间增加,但不是模型 token 暴涨的唯一来源。 +- 当前本机进程快照显示仍有高负载后台项:Codex app-server 约 60%+ CPU,WorkBuddy renderer/GPU 约 50%/20%+ CPU,一个 Next dev server 占用约 45% CPU 和 27% 内存;这些会造成“电脑算力”体感升高,但和模型 token 账单应分开看。 +- `.env.local` 当前开启 `VEDASTRO_ENABLE_NETWORK=1` 且有 endpoint;质量门默认 `skip_vedastro_live=true`,但 strict workflow / VedAstro evidence orchestrator 在真实高严谨工作流中具备出网条件。它是外部 API/网络成本风险点,不是 6月20 起 token 暴涨的主因。 +- 根因判断:没有发现占星计算核心的死循环 bug;主要 bug/设计问题是代理工作流缺少“省算力护栏”,包括过宽的文件读取输出、长线程持续携带历史、release/browser gate 被过频触发、多项目 dev server 残留,以及 VedAstro live 配置默认在本地可用时缺少显式预算提示。 + +## 2026-07-02 省算力开源工具选型与落地 + +- 全网对比后,最贴合本次根因的直接止血工具是 `squeez`:它是面向 Claude Code、Copilot CLI、OpenCode、Gemini CLI、Codex CLI 的 hook-based token compressor,重点压缩 bash/tool output、代码读取签名和重复上下文,正好对应 6月20 日后“长工具输出 + 长线程压缩摘要膨胀”的问题。 +- 已安装并验证 `squeez 1.34.4`,Codex 侧配置位于 `~/.codex/squeez/config.ini`,hooks 位于 `~/.codex/squeez/hooks/`;当前配置 `enabled=true`、`persona=ultra`、`max_lines=120`、`read_max_lines=300`、`grep_max_results=100`、`context_cache_enabled=true`、`redundancy_cache_enabled=true`。 +- 已启用 Hermes fallback 插件:`hermes plugins enable squeez-fallback` 返回成功;Codex/Hermes 下一次新 session 或重启后生效。 +- `ccusage` 更适合做用量可视化和日/项目维度追踪,已用 `npx --yes ccusage@latest --version` 验证可运行,版本 `20.0.14`;本轮未做全局安装,避免增加长期依赖。 +- `Repomix` 适合后续把仓库打包给 AI 前做 token counting 和 include/exclude 控制;它不是本次长线程工具输出膨胀的第一止血点。 +- `LiteLLM`、`Langfuse`、`Helicone` 更适合自建 API gateway、OpenAI/VedAstro/多模型调用预算和日志治理;对当前 Codex 本地 agent 会话膨胀不是最短路径,暂不接入。 diff --git a/jyotish-app/index.html b/jyotish-app/index.html index 5fb60822..e1e869cc 100644 --- a/jyotish-app/index.html +++ b/jyotish-app/index.html @@ -80,6 +80,16 @@
+
+ 入口: + + + +
+
diff --git a/jyotish-app/main.js b/jyotish-app/main.js index 5a50a983..b9cc4d5f 100644 --- a/jyotish-app/main.js +++ b/jyotish-app/main.js @@ -484,6 +484,7 @@ async function computeConsultationForBirth(birth, options = {}) { ...birth, entry_mode: entryMode, question: options.question || '', + question_text: options.questionText || options.question || '', theme: options.theme || ['career', 'marriage', 'wealth'], }; if (Array.isArray(options.events)) payload.events = options.events; @@ -532,6 +533,32 @@ function setChartComputeStatus(message = '', tone = 'warn') { function setupForm() { const form = $('birth-form'), btn = $('btn-calculate'); const btnText = btn.querySelector('.btn-text'), btnLoading = btn.querySelector('.btn-loading'); + let selectedEntryMode = 'direct_chart'; + const entryButtons = { + direct_chart: $('entry-direct-chart'), + rectification: $('entry-rectification'), + prashna: $('entry-prashna'), + }; + const prashnaQuestionGroup = $('prashna-question-group'); + const prashnaQuestionInput = $('prashna-question-text'); + const syncEntryModeUI = () => { + Object.entries(entryButtons).forEach(([mode, el]) => { + if (!el) return; + el.classList.toggle('active', mode === selectedEntryMode); + }); + if (prashnaQuestionGroup) { + prashnaQuestionGroup.style.display = selectedEntryMode === 'prashna' ? '' : 'none'; + } + }; + Object.entries(entryButtons).forEach(([mode, el]) => { + if (!el) return; + el.addEventListener('click', () => { + selectedEntryMode = mode; + syncEntryModeUI(); + }); + }); + $('entry-prashna')?.addEventListener('dblclick', () => computeConsultationForBirth(window.__jyotishBirth || {}, { entryMode: 'prashna' })); + syncEntryModeUI(); form.addEventListener('submit', async (e) => { e.preventDefault(); const year = parseInt($('birth-year').value); @@ -543,6 +570,10 @@ function setupForm() { const tz = resolveTimezoneValue($('birth-tz').value); if (!year || !month || !day || !timeVal) { alert(t('alert.date')); return; } if (isNaN(lat) || isNaN(lon)) { alert(t('alert.city')); return; } + if (selectedEntryMode === 'prashna' && !(prashnaQuestionInput?.value || '').trim()) { + alert('请输入问事问题'); + return; + } const [hour, minute, second = 0] = timeVal.split(':').map(Number); btnText.classList.add('hidden'); btnLoading.classList.remove('hidden'); btn.disabled = true; setChartComputeStatus('正在计算星盘...', 'warn'); @@ -550,7 +581,11 @@ function setupForm() { // v6.9.4: 计算层 — 优先本地 API 服务, 回退JS引擎 chartData = await computeConsultationForBirth( { year, month, day, hour, minute, second, lat, lon, tz }, - { entryMode: 'direct_chart' }, + { + entryMode: selectedEntryMode, + question: selectedEntryMode === 'prashna' ? (prashnaQuestionInput?.value || '').trim() : '', + questionText: selectedEntryMode === 'prashna' ? (prashnaQuestionInput?.value || '').trim() : '', + }, ); } catch (e) { console.error('[Jyotish] 计算失败:', e); @@ -746,6 +781,8 @@ function renderAll() { if (!rectPrompt.dataset.bound) { rectPrompt.dataset.bound = 'true'; $('rect-prompt-btn').addEventListener('click', () => { + selectedEntryMode = 'rectification'; + syncEntryModeUI(); rectPanel.classList.remove('hidden'); if (rectOverlay) rectOverlay.classList.remove('hidden'); document.body.style.overflow = 'hidden'; @@ -963,6 +1000,7 @@ function renderWorkflowSummaryCard(workflow = {}) { const runtimePlanner = workflow?.runtime_planner || {}; const unified = workflow?.unified_orchestrator || {}; const sourcePriority = workflow?.source_priority?.priority || unified?.source_priority?.priority || []; + const muhurtaSidecar = workflow?.muhurta_panchanga; const compactSource = sourcePriority.length ? sourcePriority.join(' -> ') : 'VedAstro official / local supplemental / fallback'; @@ -983,10 +1021,113 @@ function renderWorkflowSummaryCard(workflow = {}) { source_priority ${escapeHtml(compactSource)}
+
+ timing_sidecar + ${escapeHtml(muhurtaSidecar?.status === 'ok' + ? `run_muhurta_panchanga · ${muhurtaSidecar.activity || 'business'} · ${muhurtaSidecar.panchanga?.query_date || '-'}` + : 'not_used')} +
+ ${renderWorkflowSidecarsSummary(workflow)} `; } +function renderWorkflowSidecarsSummary(workflow = {}) { + const muhurta = workflow?.muhurta_panchanga || {}; + const prashna = workflow?.prashna || {}; + const remedies = workflow?.audited_remedies || {}; + const muhurtaSummary = muhurta?.panchanga?.summary || {}; + const muhurtaWindows = Array.isArray(muhurta?.best_windows) ? muhurta.best_windows : []; + const topWindow = muhurtaWindows[0] || null; + const prashnaConclusion = prashna?.summary?.conclusion || prashna?.kp_answer_v2?.kp_answer || prashna?.kp_answer?.kp_answer || ''; + const prashnaConfidence = prashna?.summary?.confidence || prashna?.kp_answer_v2?.confidence || prashna?.kp_answer?.confidence || ''; + const remedyRecommendations = remedies?.raw?.recommendations || remedies?.raw || {}; + const remedyCount = ['gems', 'mantras', 'donations', 'fasting', 'lifestyle', 'dosha_remedies'] + .reduce((total, key) => total + (Array.isArray(remedyRecommendations?.[key]) ? remedyRecommendations[key].length : 0), 0); + const sidecars = [ + ` +
+
+ Muhurta / Panchanga + ${escapeHtml(muhurta?.status === 'ok' ? (muhurta.activity_label || muhurta.activity || '-') : '未启用')} +
+

${escapeHtml(muhurta?.status === 'ok' + ? `${muhurta?.panchanga?.query_date || muhurta?.date || '-'} · ${muhurtaSummary?.quality || '待判定'}` + : '当前问题未调用时间择日侧链。')}

+
+ 建议窗口 + ${escapeHtml(topWindow ? `${topWindow.start || '-'}-${topWindow.end || '-'} · ${topWindow.quality || 'window'}` : '暂无明确吉时窗口')} +
+
+ 避开时段 + ${escapeHtml([muhurta?.panchanga?.inauspicious_periods?.rahu_kala, muhurta?.panchanga?.inauspicious_periods?.yamaganda].filter(Boolean).join(' · ') || '未返回')} +
+ +
+ `, + ` +
+
+ Prashna 问事 + ${escapeHtml(prashnaConclusion || '未启用')} +
+

${escapeHtml(workflow?.question || prashna?.question || '当前流程没有附带问事问题。')}

+
+ 置信度 + ${escapeHtml(prashnaConfidence || '未返回')} +
+
+ 问题宫 / 主星 + ${escapeHtml(`${prashna?.summary?.primary_house || prashna?.kp_answer_v2?.primary_house || prashna?.kp_answer?.primary_house || '-'}宫 · ${prashna?.summary?.question_lord || prashna?.kp_answer_v2?.question_lord || prashna?.kp_answer?.question_lord || '-'}`)} +
+ +
+ `, + ` +
+
+ 补救建议 + ${escapeHtml(remedies?.status === 'ok' ? (remedies?.topic || 'general') : '未启用')} +
+

${escapeHtml(remedies?.status === 'ok' + ? `来源 ${remedies?.source || 'strict_audit_gate'} · 当前主运 ${remedies?.active_dasha_lord || '-'}` + : '当前流程没有生成严格门控后的补救建议。')}

+
+ 行动数量 + ${escapeHtml(remedies?.status === 'ok' ? `${remedyCount} 条` : '0 条')} +
+
+ 门控来源 + ${escapeHtml(remedies?.status === 'ok' ? 'strict_audit_gate' : 'not_used')} +
+ +
+ `, + ]; + return `
${sidecars.join('')}
`; +} + +function bindWorkflowSummaryActions(host) { + if (!host || host.dataset.boundWorkflowSidecars === 'true') return; + host.dataset.boundWorkflowSidecars = 'true'; + host.addEventListener('click', event => { + const btn = event.target.closest('[data-open-tab]'); + if (!btn || btn.disabled) return; + const tabName = btn.dataset.openTab; + const scrollTarget = btn.dataset.scrollTarget; + if (tabName === 'prashna') switchToTab('prashna'); + else if (tabName === 'remedies') switchToTab('remedies'); + else if (tabName === 'provenance') switchToTab('provenance'); + else switchToTab(tabName); + if (scrollTarget) { + setTimeout(() => { + const target = document.querySelector(scrollTarget); + target?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 120); + } + }); +} + function renderWorkflowSummaryPanel(chartRecord) { const host = $('workflow-summary-panel'); if (!host) return; @@ -996,6 +1137,7 @@ function renderWorkflowSummaryPanel(chartRecord) { return; } host.innerHTML = renderWorkflowSummaryCard(workflow); + bindWorkflowSummaryActions(host); } // ============================================================================ @@ -2175,6 +2317,8 @@ function renderProvenancePanel({ chartData, panchanga, provenance, validation, a function renderWorkflowProvenancePanel(chartRecord) { const workflow = getConsultationWorkflow(chartRecord); const runtimePlanner = getRuntimePlanner(chartRecord); + const runtimeTruth = workflow.runtime_truth || {}; + const sourceCoverage = workflow.interpretation_source_runtime_coverage || {}; if (!Object.keys(workflow).length && !Object.keys(runtimePlanner).length) return ''; const route = runtimePlanner.route || workflow.routing || {}; const unified = workflow.unified_orchestrator || {}; @@ -2210,6 +2354,23 @@ function renderWorkflowProvenancePanel(chartRecord) { ${escapeHtml(`async: ${asyncCandidates.join(' / ') || 'none'}`)} ${escapeHtml(runtimePlanner.boundary || workflow.boundary || '')}
+
+ VedAstro Runtime Truth + ${escapeHtml(runtimeTruth.catalog_boundary || '-')} + ${escapeHtml([ + `chart_core:${runtimeTruth.official_execution_layers?.chart_core || '-'}`, + `event_radar:${runtimeTruth.official_execution_layers?.event_radar || '-'}`, + `catalog:${runtimeTruth.official_execution_layers?.catalog_status || '-'}`, + ].join(' · '))} +
+
+ Interpretation Source Coverage + ${escapeHtml(sourceCoverage.source_pack_status || '-')} + ${escapeHtml([ + `runtime_visibility_status:${sourceCoverage.runtime_visibility_status || '-'}`, + `markers:${(sourceCoverage.proven_runtime_markers || []).join(' / ') || '-'}`, + ].join(' · '))} +
`; @@ -5429,7 +5590,21 @@ function renderVimshottariAnalysisBlock(analysis) { function renderRemediesTab(chartData) { const sec = document.getElementById('remedies-section'); if (!sec) return; - const remedies = chartData?.remedies || chartData?._extended?.remedies; + const workflow = chartData?._consultationWorkflow; + const auditedRemedies = workflow?.audited_remedies; + const remedies = chartData?.remedies || chartData?._extended?.remedies || ( + auditedRemedies?.status === 'ok' + ? { + recommendations: auditedRemedies.raw?.recommendations || auditedRemedies.raw || {}, + evidence_chain: { + source: auditedRemedies.source || 'strict_audit_gate', + topic: auditedRemedies.topic || 'general', + active_dasha_lord: auditedRemedies.active_dasha_lord || '', + gate: 'strict_audit_gate', + }, + } + : null + ); if (!remedies) { sec.innerHTML = `
@@ -5452,6 +5627,9 @@ function renderRemediesTab(chartData) { const primaryDonation = donations[0]; const primaryLifestyle = lifestyle[0]; const totalActions = gems.length + mantras.length + donations.length + fasting.length + lifestyle.length + doshaRemedies.length; + const auditedHeader = auditedRemedies?.status === 'ok' + ? `
统一主链补救

来源 ${escapeHtml(auditedRemedies.source || 'strict_audit_gate')} · 主题 ${escapeHtml(auditedRemedies.topic || 'general')} · 当前主运 ${escapeHtml(auditedRemedies.active_dasha_lord || '-')}

` + : ''; const renderActionCards = (items, mapper) => items.map(item => { const mapped = mapper(item); @@ -5516,6 +5694,7 @@ function renderRemediesTab(chartData) { `).join(''); sec.innerHTML = ` + ${auditedHeader}
@@ -7015,8 +7194,13 @@ function renderPrashnaTab(chartData) { const result = $('prashna-result'); const runBtn = $('btn-run-prashna'); if (!category || !question || !result || !runBtn) return; + const workflow = chartData?._consultationWorkflow; renderPrashnaCaseWorkspace(); setupPrashnaCaseActions(); + if (workflow?.prashna) { + result.innerHTML = renderPrashnaResult(workflow.prashna, workflow.prashna.question || workflow.question || ''); + bindTerms(result); + } if (!runBtn.dataset.bound) { runBtn.dataset.bound = 'true'; diff --git a/jyotish-app/style.css b/jyotish-app/style.css index 05730427..c0132e09 100644 --- a/jyotish-app/style.css +++ b/jyotish-app/style.css @@ -5471,6 +5471,22 @@ body { font-family: var(--font-body); background: var(--bg-page); color: var(--t color: var(--text-heading); font-size: 12px; } +.workflow-sidecars-summary { + margin-top: 12px; +} +.sidecar-jump-btn { + margin-top: 10px; + padding: 8px 10px; + border: 1px solid var(--border-light); + border-radius: var(--radius-sm); + background: var(--bg-surface); + color: var(--text-heading); + cursor: pointer; +} +.sidecar-jump-btn:disabled { + opacity: 0.55; + cursor: not-allowed; +} .workflow-provenance-panel { margin-top: 12px; } diff --git a/mcp_server.py b/mcp_server.py index 36b5516f..20c74336 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -183,11 +183,96 @@ BLOCKED_NON_RUNTIME_SOURCE_REFS = [ ] REMAINING_PRIORITY1_BATCH_QUEUE = [ - "real_case_studies_batch1", - "rishi_ai_mcp_batch1", - "vedic_astro_skills_batch1", "references_batch2", + "vedastro_official_default_closure", + "external_oracle_parity_batch", + "install_usage_path_slimming", ] +REAL_CASE_STUDIES_BATCH1_INDEX = { + "career": [ + "references/real_case_studies/vedicka/career-success-poverty-prosperity.md", + "references/real_case_studies/印度占星修正版研究结论v3-高压基建后的反转兑现模型.md", + ], + "finance": [ + "references/real_case_studies/vedicka/career-success-poverty-prosperity.md", + "docs/benchmark/public_jyotish_benchmark_dashboard.json", + ], + "relationship": [ + "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json", + "docs/benchmark/legacy-marriage-v6.1/印度占星实战案例综合验证报告-v6.1-2026-05-03.md", + ], + "health": [ + "references/real_case_studies/印度占星解盘与推运误区反思报告-2026-04-22.md", + ], + "rectification": [ + "references/birth-time-rectification-cases.md", + ], + "timing": [ + "docs/benchmark/dasha_external_oracle_closure_status.json", + "docs/benchmark/tajika_sahams_annual_closure_status.json", + ], +} +REAL_CASE_STUDIES_BATCH1_SOURCE_REFS = sorted( + {path for paths in REAL_CASE_STUDIES_BATCH1_INDEX.values() for path in paths} +) +RISHI_AI_MCP_BATCH1_DOMAIN_MAP = { + "career": [ + "references/open_source_sources/rishi-ai-mcp/.agents/skills/career-analysis/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/career-analysis.md", + ], + "finance": [ + "references/open_source_sources/rishi-ai-mcp/.agents/skills/finance-analysis/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/finance-analysis.md", + ], + "relationship": [ + "references/open_source_sources/rishi-ai-mcp/.agents/skills/relationship-analysis/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/skills/marriage-analysis/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/skills/spouse-profiling/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/relationship-analysis.md", + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/marriage-analysis.md", + ], + "children": [ + "references/open_source_sources/rishi-ai-mcp/.agents/skills/children-analysis/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/children-analysis.md", + ], + "health": [ + "references/open_source_sources/rishi-ai-mcp/.agents/skills/health-analysis/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/health-analysis.md", + ], + "full_reading": [ + "references/open_source_sources/rishi-ai-mcp/.agents/skills/full-reading/SKILL.md", + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/full-reading.md", + "references/open_source_sources/rishi-ai-mcp/.agents/rules/rishi-ai.md", + ], +} +RISHI_AI_MCP_BATCH1_SOURCE_REFS = sorted( + {path for paths in RISHI_AI_MCP_BATCH1_DOMAIN_MAP.values() for path in paths} +) +VEDIC_ASTRO_SKILLS_BATCH1_DOMAIN_MAP = { + "core": [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/SKILL.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/report_rules.md", + ], + "reader_validation": [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/SKILL.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/data_contract.md", + ], + "career": [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-career/SKILL.md", + ], + "relationship": [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-love/SKILL.md", + ], + "rectification": [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-rectifier/SKILL.md", + ], + "calculator": [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-calculator/SKILL.md", + ], +} +VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS = sorted( + {path for paths in VEDIC_ASTRO_SKILLS_BATCH1_DOMAIN_MAP.values() for path in paths} +) def _domain_invocation_layers() -> Dict[str, Any]: @@ -432,6 +517,9 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: *core_rule_paths, *promote_batch2_paths, *reference_only_paths, + *REAL_CASE_STUDIES_BATCH1_SOURCE_REFS, + *RISHI_AI_MCP_BATCH1_SOURCE_REFS, + *VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS, *frontend_interpretation_paths, *planet_house_paths, qa_rules_path, @@ -493,6 +581,58 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: "promotion_status": "reference_only", "boundary": "Reference-only sources can explain conflicts but cannot override primary rule sources.", }, + "real_case_calibration_layer": { + "status": "queued", + "batch_id": "real_case_studies_batch1", + "index_status": "available" + if all(_repo_relative_exists(path) for path in REAL_CASE_STUDIES_BATCH1_SOURCE_REFS) + else "partial", + "domain_buckets": list(REAL_CASE_STUDIES_BATCH1_INDEX.keys()), + "source_refs": REAL_CASE_STUDIES_BATCH1_SOURCE_REFS, + "case_index_by_domain": REAL_CASE_STUDIES_BATCH1_INDEX, + "retrieval_policy": "domain_bucket_first_then_case_quality_gate", + "promotion_status": "local_case_retrieval_layer", + "boundary": "Local case index is callable for calibration; matching-case attachment remains required before lifting confidence.", + }, + "rishi_ai_mcp_batch1_layer": { + "status": "available" + if all(_repo_relative_exists(path) for path in RISHI_AI_MCP_BATCH1_SOURCE_REFS) + else "partial", + "batch_id": "rishi_ai_mcp_batch1", + "domain_map": RISHI_AI_MCP_BATCH1_DOMAIN_MAP, + "source_refs": RISHI_AI_MCP_BATCH1_SOURCE_REFS, + "promotion_status": "open_source_reference_layer", + "runtime_truth_status": "not_primary_truth", + "boundary": "Use as workflow/reference guidance only; do not override local strict rules or oracle-calibrated calculations.", + }, + "vedic_astro_skills_batch1_layer": { + "status": "available" + if all(_repo_relative_exists(path) for path in VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS) + else "partial", + "batch_id": "vedic_astro_skills_batch1", + "domain_map": VEDIC_ASTRO_SKILLS_BATCH1_DOMAIN_MAP, + "source_refs": VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS, + "promotion_status": "external_skill_reference_layer", + "runtime_truth_status": "not_primary_truth", + "boundary": "Use as external skill-corpus reference only; already-wired QA/Yoga/reader files remain separately governed.", + }, + "external_closure_gap_layer": { + "vedastro_official": { + "status": "blocked", + "reason": "official full snapshot/default closure is not yet guaranteed for every strict workflow route.", + "next_action": "stabilize official snapshot cache TTL/free-tier queue and route-level fallback reporting.", + }, + "oracle_parity": { + "status": "blocked", + "systems": ["VedAstro", "PyJHora", "jyotishganit"], + "priority_domains": ["Dasha", "Shadbala", "Tajika", "Narayana"], + "next_action": "expand external oracle parity packets without treating unmatched outputs as truth.", + }, + "install_usage_path": { + "status": "needs_slimming", + "next_action": "keep one stable user entry command with official extended env, cache/TTL, free-tier queue, and strict workflow defaults.", + }, + }, "blocked_non_runtime_layer": { "status": "blocked", "source_refs": blocked_non_runtime_paths, @@ -547,6 +687,9 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: "status": "blocked", "required": True, "source_ref": real_case_checklist_path, + "local_index_status": "available", + "local_batch_id": "real_case_studies_batch1", + "local_case_source_refs": REAL_CASE_STUDIES_BATCH1_SOURCE_REFS, "effect_on_confidence": "caps_confidence_without_matching_cases", }, "boundary": ( @@ -2933,38 +3076,15 @@ def _build_mevg_collection_queue(route: str, strict: Dict[str, Any]) -> Dict[str def _build_real_case_calibration_layer(route: str, strict: Dict[str, Any]) -> Dict[str, Any]: - case_index_by_domain = { - "career": [ - "references/real_case_studies/vedicka/career-success-poverty-prosperity.md", - "references/real_case_studies/印度占星修正版研究结论v3-高压基建后的反转兑现模型.md", - ], - "finance": [ - "references/real_case_studies/vedicka/career-success-poverty-prosperity.md", - "docs/benchmark/public_jyotish_benchmark_dashboard.json", - ], - "relationship": [ - "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json", - "docs/benchmark/legacy-marriage-v6.1/印度占星实战案例综合验证报告-v6.1-2026-05-03.md", - ], - "health": [ - "references/real_case_studies/印度占星解盘与推运误区反思报告-2026-04-22.md", - ], - "rectification": [ - "references/birth-time-rectification-cases.md", - ], - "timing": [ - "docs/benchmark/dasha_external_oracle_closure_status.json", - "docs/benchmark/tajika_sahams_annual_closure_status.json", - ], - } return { "status": "queued", "route": route, "batch_id": "real_case_studies_batch1", "index_status": "available", - "domain_buckets": ["career", "finance", "relationship", "health", "rectification", "timing"], + "domain_buckets": list(REAL_CASE_STUDIES_BATCH1_INDEX.keys()), "source_roots": ["references/real_case_studies", "docs/benchmark"], - "case_index_by_domain": case_index_by_domain, + "case_index_by_domain": REAL_CASE_STUDIES_BATCH1_INDEX, + "source_refs": REAL_CASE_STUDIES_BATCH1_SOURCE_REFS, "retrieval_policy": "domain_bucket_first_then_case_quality_gate", "confidence_effect": "caps_confidence_until_matching_cases_are_attached", "fallback_policy": "downgrade_without_matching_cases", @@ -4132,6 +4252,41 @@ def strict_workflow( ) result["chart"] = chart result["strict_workflow"] = _collect_strict_evidence(route, chart) + try: + from jyotish_api_server import JyotishAPIHandler + + handler = JyotishAPIHandler.__new__(JyotishAPIHandler) + vedastro_official = handler._high_rigor_vedastro_official_summary(chart) + interpretation_coverage = handler._interpretation_source_runtime_coverage(chart) + machine_evidence_packet = _UNIFIED_CONSULTATION_ORCHESTRATOR.machine_evidence_packet( + chart=chart, + route_packet=result.get("routing") if isinstance(result.get("routing"), dict) else route_packet, + vedastro_official=vedastro_official, + ) + real_case_calibration = _UNIFIED_CONSULTATION_ORCHESTRATOR.real_case_calibration_catalog( + route_packet=result.get("routing") if isinstance(result.get("routing"), dict) else route_packet, + machine_evidence_packet=machine_evidence_packet, + ) + planner = result.get("runtime_planner") if isinstance(result.get("runtime_planner"), dict) else {} + result["vedastro_official"] = vedastro_official + result["runtime_truth"] = vedastro_official.get("runtime_truth", {}) + result["interpretation_source_runtime_coverage"] = interpretation_coverage + result["machine_evidence_packet"] = machine_evidence_packet + result["real_case_calibration"] = real_case_calibration + result["runtime_evidence_log"] = _UNIFIED_CONSULTATION_ORCHESTRATOR.runtime_evidence_log( + surface="skill_mcp", + entry_mode=result.get("entry_mode", "direct_chart"), + route_packet=result.get("routing") if isinstance(result.get("routing"), dict) else route_packet, + executed_steps=planner.get("executed_steps", []), + skipped_steps=planner.get("skipped_steps", []), + vedastro_official=vedastro_official, + interpretation_source_runtime_coverage=interpretation_coverage, + machine_evidence_packet=machine_evidence_packet, + real_case_calibration=real_case_calibration, + blind=False, + ) + except Exception: + pass return result diff --git a/progress.md b/progress.md index 660360b0..e4010819 100644 --- a/progress.md +++ b/progress.md @@ -858,3 +858,21 @@ - 第一批结果:`promote_to_reference_pack_candidate=9`、`reference_only=3`;已完成 batch1 级别的 source-pack eligibility 仲裁。 - 仲裁结论:`reference_pack_candidate=9`、`reference_only=3`、`runtime_truth_ready_count=0`;不允许直接进入 runtime truth。 - 下一步必须做:把 9 个候选显式接入 interpretation source/source pack,并加 strict workflow visibility tests;内容级规则优先级仍不得压过核心主规则。 + +## 2026-07-02T23:30:00+08:00 - 6月20日后算力消耗升高排查 + +- 用户要求检查“6月20日开始为什么算力消耗增高,是什么 bug”。 +- 已按系统化调试排查:Git 变更、质量门/CI、Codex 本地 session/token metadata、WorkBuddy trace 元数据、当前进程负载、VedAstro live 配置。 +- 关键证据:Codex `session_index.jsonl` 从 2026-06-20 起出现多项目长线程;`.codex/archived_sessions` 里 2026-06-21/22 有 139MB、127MB、58MB 等大 session;`token_count` 聚合显示 2026-06-22/23 单日 archived sessions last total tokens 分别约 556.6M/580.5M。 +- 排查结论已写入 `findings.md`:主因不是占星核心计算死循环,而是代理工作流/上下文管理设计导致的大范围读取、长工具输出、压缩摘要膨胀、质量门变重、多项目 dev server 残留。 +- 未改代码或配置;后续若要止血,优先加省算力 profile、限制工具输出、拆短线程、关闭残留 dev server,并把 `VEDASTRO_ENABLE_NETWORK` 改成显式按需开启。 + +## 2026-07-02T23:42:00+08:00 - 全网省算力开源工具安装 + +- 用户要求直接全网寻找最合适的省算力开源项目并解决问题。 +- 对比 `squeez`、`ccusage`、`Repomix`、`LiteLLM` 等项目后,优先选择 `squeez`,因为本次根因是 Codex/Agent 工具输出和长上下文膨胀,而不是单纯 API gateway 计费问题。 +- 已通过官方安装脚本安装 `squeez 1.34.4`;Codex 配置与 hooks 已写入 `~/.codex/squeez/`。 +- 已验证本地二进制:`~/.claude/squeez/bin/squeez --version` 输出 `squeez 1.34.4`。 +- 已启用 Hermes fallback 插件:`hermes plugins enable squeez-fallback` 成功,提示下一 session 生效。 +- 已验证 `ccusage` 可通过 `npx` 临时运行,版本 `20.0.14`;本轮未全局安装。 +- 仍需用户重启 Codex/相关 CLI,让已安装 hooks 在新 session 中生效。 diff --git a/scripts/diagnose_external_engine_adapters.py b/scripts/diagnose_external_engine_adapters.py new file mode 100644 index 00000000..7ee0ef05 --- /dev/null +++ b/scripts/diagnose_external_engine_adapters.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Aggregate external-engine adapter readiness diagnostics.""" + +from __future__ import annotations + +import argparse +import json + +try: + from diagnose_vedastro_mode import build_report as build_vedastro_report + from diagnose_pyjhora_adapter import build_report as build_pyjhora_report + from diagnose_jyotishganit_adapter import build_report as build_jyotishganit_report +except Exception: # pragma: no cover - import path varies in tests/CLI + from scripts.diagnose_vedastro_mode import build_report as build_vedastro_report + from scripts.diagnose_pyjhora_adapter import build_report as build_pyjhora_report + from scripts.diagnose_jyotishganit_adapter import build_report as build_jyotishganit_report + + +def build_report() -> dict: + vedastro = build_vedastro_report() + pyjhora = build_pyjhora_report() + jyotishganit = build_jyotishganit_report() + engines = { + "VedAstro": { + "status": "available" if vedastro["official_ready"] else "blocked", + "mode": vedastro["mode"], + "readiness_blockers": vedastro["readiness_blockers"], + "official_closure_plan": vedastro.get("official_closure_plan", {}), + }, + "PyJHora/JHora": { + "status": pyjhora["status"], + "adapter_command": pyjhora["adapter_command"], + "missing_dependency": pyjhora["missing_dependency"], + "install_hint": pyjhora.get("install_hint", {}), + "license_boundary": pyjhora.get("license_boundary"), + "ephemeris_data_note": pyjhora.get("ephemeris_data_note"), + }, + "jyotishganit": { + "status": jyotishganit["status"], + "adapter_path": jyotishganit["adapter_path"], + "license": jyotishganit["license"], + }, + } + return { + "scope": "external_engine_adapter_diagnostics", + "status": "complete" if all(engine["status"] == "available" for engine in engines.values()) else "partial", + "engines": engines, + "boundary": "Readiness diagnostics only; this does not run a three-engine consultation comparison.", + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--json", action="store_true", help="print machine-readable JSON") + args = parser.parse_args() + report = build_report() + if args.json: + print(json.dumps(report, ensure_ascii=False, sort_keys=True)) + else: + print(f"External engine adapter status: {report['status']}") + for name, engine in report["engines"].items(): + print(f"{name}: {engine['status']}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/diagnose_jyotishganit_adapter.py b/scripts/diagnose_jyotishganit_adapter.py new file mode 100644 index 00000000..4e80824e --- /dev/null +++ b/scripts/diagnose_jyotishganit_adapter.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Report whether the local jyotishganit reference checkout is importable.""" + +from __future__ import annotations + +import argparse +import importlib.util +import json +import os +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_ADAPTER_RELATIVE = "references/open_source_sources/jyotishganit" + + +def build_report() -> dict: + adapter_relative = os.environ.get("JYOTISHGANIT_ADAPTER_PATH", DEFAULT_ADAPTER_RELATIVE).strip() or DEFAULT_ADAPTER_RELATIVE + adapter = ROOT / adapter_relative + package_dir = adapter / "jyotishganit" + license_file = adapter / "LICENSE" + + if not adapter.exists() or not package_dir.exists(): + status = "missing_checkout" + importable = False + error = None + else: + sys.path.insert(0, str(adapter)) + try: + importable = importlib.util.find_spec("jyotishganit") is not None + status = "available" if importable else "runtime_error" + error = None if importable else "package_not_importable" + except Exception as exc: # pragma: no cover - defensive diagnostic + importable = False + status = "runtime_error" + error = f"{type(exc).__name__}: {exc}" + finally: + try: + sys.path.remove(str(adapter)) + except ValueError: + pass + + return { + "scope": "jyotishganit_adapter_diagnostics", + "status": status, + "adapter_path": adapter_relative, + "checkout_exists": adapter.exists(), + "package_exists": package_dir.exists(), + "importable": importable, + "license": "MIT" if license_file.exists() else "unknown", + "error": error, + "boundary": "This is a reference-checkout readiness smoke check only; it does not run jyotishganit calculations.", + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--json", action="store_true", help="print machine-readable JSON") + args = parser.parse_args() + report = build_report() + if args.json: + print(json.dumps(report, ensure_ascii=False, sort_keys=True)) + else: + print(f"jyotishganit adapter status: {report['status']}") + print(f"Adapter path: {report['adapter_path']}") + if report["error"]: + print(f"Error: {report['error']}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/diagnose_pyjhora_adapter.py b/scripts/diagnose_pyjhora_adapter.py new file mode 100644 index 00000000..d194602c --- /dev/null +++ b/scripts/diagnose_pyjhora_adapter.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Report whether the PyJHora comparison adapter can run in this environment.""" + +from __future__ import annotations + +import argparse +import importlib.util +import json +import os +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ADAPTER_RELATIVE = "benchmarks/jyotish/scripts/run_pyjhora_compare.py" + + +def build_report() -> dict: + adapter = ROOT / ADAPTER_RELATIVE + module_name = os.environ.get("PYJHORA_MODULE_NAME", "jhora").strip() or "jhora" + adapter_exists = adapter.exists() + module_available = importlib.util.find_spec(module_name) is not None + + if not adapter_exists: + status = "missing_adapter" + elif not module_available: + status = "missing_dependency" + else: + status = "available" + + return { + "scope": "pyjhora_adapter_diagnostics", + "status": status, + "adapter_command": f"python3 {ADAPTER_RELATIVE}", + "adapter_exists": adapter_exists, + "dependency_module": module_name, + "dependency_available": module_available, + "missing_dependency": None if module_available else module_name, + "install_hint": { + "package": "PyJHora", + "commands": ["pip install PyJHora"], + "note": "Install in an isolated optional benchmark environment, not as a hard runtime dependency.", + }, + "license_boundary": "AGPL external benchmark only; do not vendor or make it a runtime dependency.", + "ephemeris_data_note": "Recent PyJHora releases may require separate Swiss Ephemeris data download/configuration before full chart comparison can run.", + "boundary": "This is an adapter readiness smoke check only; it does not run PyJHora chart comparison.", + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--json", action="store_true", help="print machine-readable JSON") + args = parser.parse_args() + report = build_report() + if args.json: + print(json.dumps(report, ensure_ascii=False, sort_keys=True)) + else: + print(f"PyJHora adapter status: {report['status']}") + print(f"Adapter command: {report['adapter_command']}") + if report["missing_dependency"]: + print(f"Missing dependency: {report['missing_dependency']}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/diagnose_vedastro_mode.py b/scripts/diagnose_vedastro_mode.py index 10b1ef9b..bd3a6c43 100644 --- a/scripts/diagnose_vedastro_mode.py +++ b/scripts/diagnose_vedastro_mode.py @@ -38,13 +38,20 @@ def build_report() -> dict: network_enabled = _bool_env("VEDASTRO_ENABLE_NETWORK") timeout_seconds = _timeout_seconds() has_api_key = bool(os.environ.get("VEDASTRO_API_KEY", "").strip()) + free_tier_queue_enabled = _bool_env("VEDASTRO_FREE_TIER_QUEUE") or _bool_env("VEDASTRO_FREE_TIER_QUEUE_ENABLED") or _bool_env("VEDASTRO_ENABLE_FREE_TIER_QUEUE") missing = [] + readiness_blockers = [] if not endpoint: missing.append("VEDASTRO_API_ENDPOINT") + readiness_blockers.append("missing_endpoint") if not network_enabled: missing.append("VEDASTRO_ENABLE_NETWORK=1") + readiness_blockers.append("network_disabled") if timeout_seconds <= FAST_TIMEOUT_THRESHOLD_SECONDS: missing.append("VEDASTRO_TIMEOUT_SECONDS>5") + readiness_blockers.append("timeout_too_low") + if endpoint and network_enabled and not has_api_key: + readiness_blockers.append("premium_key_missing") official_ready = not missing mode = "official_extended" if official_ready else "fast_local_fallback" return { @@ -54,12 +61,26 @@ def build_report() -> dict: "network_enabled": network_enabled, "timeout_seconds": timeout_seconds, "has_api_key": has_api_key, + "free_tier_queue_enabled": free_tier_queue_enabled, + "free_tier_possible_with_cache_queue": bool(endpoint and network_enabled and timeout_seconds > FAST_TIMEOUT_THRESHOLD_SECONDS), "missing": missing, + "readiness_blockers": readiness_blockers, "expected_fallback_status": ( "none_if_official_endpoint_responds" if official_ready else "official_snapshot_budget_exhausted_or_endpoint_blocked" ), + "official_closure_plan": { + "required_env": { + "VEDASTRO_API_ENDPOINT": endpoint or "https://api.vedastro.org/api", + "VEDASTRO_ENABLE_NETWORK": "1", + "VEDASTRO_TIMEOUT_SECONDS": "20", + "VEDASTRO_API_KEY": "optional_but_recommended_for_stable_full_snapshot", + }, + "free_tier_policy": "Queue/cache can reduce throttling, but free tier may still return blocked or partial snapshots.", + "premium_key_policy": "API key recommended for stable official full snapshot; free tier may still block or throttle.", + "raw_response_acceptance": "vedastro_official.raw_response must be present before claiming official cloud closure.", + }, "next_step": ( "Run full-reading or strict_workflow; verify vedastro_official.status is ok/partial." if official_ready @@ -82,6 +103,10 @@ def main() -> int: print(f"network_enabled: {str(report['network_enabled']).lower()}") print(f"timeout_seconds: {report['timeout_seconds']}") print(f"has_api_key: {str(report['has_api_key']).lower()}") + print(f"free_tier_possible_with_cache_queue: {str(report['free_tier_possible_with_cache_queue']).lower()}") + print(f"free_tier_queue_enabled: {str(report['free_tier_queue_enabled']).lower()}") + if report["readiness_blockers"]: + print(f"readiness_blockers: {', '.join(report['readiness_blockers'])}") if report["missing"]: print("missing:") for item in report["missing"]: @@ -93,4 +118,3 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) - diff --git a/scripts/export_chart_research_pdf.py b/scripts/export_chart_research_pdf.py new file mode 100644 index 00000000..d2ba584c --- /dev/null +++ b/scripts/export_chart_research_pdf.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +"""Export the chart research book root to a simple readable PDF.""" + +from __future__ import annotations + +import argparse +import re +from pathlib import Path + +from reportlab.lib.colors import HexColor +from reportlab.lib.enums import TA_CENTER +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import mm +from reportlab.pdfbase.pdfmetrics import registerFont +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.platypus import PageBreak, Paragraph, Preformatted, SimpleDocTemplate, Spacer + + +FONT_CANDIDATES = [ + ("/System/Library/Fonts/Supplemental/Songti.ttc", 0), + ("/System/Library/Fonts/Supplemental/Arial Unicode.ttf", 0), + ("/Library/Fonts/Arial Unicode.ttf", 0), +] + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Export chart research book root to PDF") + parser.add_argument( + "--report-root", + default="docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME", + help="Path to the chart research report root", + ) + parser.add_argument( + "--output", + default=None, + help="Output PDF path; defaults to /exports/chart_research_REDACTED_DATE_REDACTED_TIME.pdf", + ) + return parser.parse_args() + + +def _load_order(book_path: Path) -> list[Path]: + text = book_path.read_text(encoding="utf-8") + links = re.findall(r"\]\(\./([^)]+\.md)\)", text) + return [book_path.parent / link for link in links] + + +def _clean_inline(text: str) -> str: + text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text) + text = text.replace("`", "") + text = text.replace("**", "") + text = text.replace("*", "") + return text.strip() + + +def _register_book_font() -> str: + font_name = "ChartResearchBookFont" + for font_path, subfont_index in FONT_CANDIDATES: + path = Path(font_path) + if not path.exists(): + continue + registerFont(TTFont(font_name, str(path), subfontIndex=subfont_index)) + return font_name + raise RuntimeError( + "No renderable CJK font found for PDF export. Tried: " + + ", ".join(path for path, _ in FONT_CANDIDATES) + ) + + +def _styles(): + font_name = _register_book_font() + base = getSampleStyleSheet() + title = ParagraphStyle( + "BookTitle", + parent=base["Title"], + fontName=font_name, + fontSize=22, + leading=28, + alignment=TA_CENTER, + textColor=HexColor("#2c241c"), + spaceAfter=18, + ) + h1 = ParagraphStyle( + "H1", + parent=base["Heading1"], + fontName=font_name, + fontSize=18, + leading=24, + textColor=HexColor("#2f2a24"), + spaceBefore=10, + spaceAfter=8, + ) + h2 = ParagraphStyle( + "H2", + parent=base["Heading2"], + fontName=font_name, + fontSize=14, + leading=20, + textColor=HexColor("#3d352c"), + spaceBefore=8, + spaceAfter=6, + ) + h3 = ParagraphStyle( + "H3", + parent=base["Heading3"], + fontName=font_name, + fontSize=12, + leading=17, + textColor=HexColor("#4a4036"), + spaceBefore=6, + spaceAfter=4, + ) + body = ParagraphStyle( + "Body", + parent=base["BodyText"], + fontName=font_name, + fontSize=10.5, + leading=16, + textColor=HexColor("#332b24"), + spaceAfter=4, + ) + bullet = ParagraphStyle( + "Bullet", + parent=body, + leftIndent=10, + firstLineIndent=0, + ) + mono = ParagraphStyle( + "Mono", + parent=body, + fontName=font_name, + fontSize=8.8, + leading=12, + ) + return {"title": title, "h1": h1, "h2": h2, "h3": h3, "body": body, "bullet": bullet, "mono": mono} + + +def _render_markdown(md_path: Path, styles: dict[str, ParagraphStyle]) -> list: + text = md_path.read_text(encoding="utf-8") + lines = text.splitlines() + flow = [] + i = 0 + + while i < len(lines): + line = lines[i].rstrip() + stripped = line.strip() + if not stripped: + flow.append(Spacer(1, 4)) + i += 1 + continue + + if stripped.startswith("|"): + block = [] + while i < len(lines) and lines[i].strip().startswith("|"): + block.append(lines[i].rstrip()) + i += 1 + flow.append(Preformatted("\n".join(block), styles["mono"])) + flow.append(Spacer(1, 5)) + continue + + if stripped.startswith("# "): + flow.append(Paragraph(_clean_inline(stripped[2:]), styles["h1"])) + i += 1 + continue + + if stripped.startswith("## "): + flow.append(Paragraph(_clean_inline(stripped[3:]), styles["h2"])) + i += 1 + continue + + if stripped.startswith("### "): + flow.append(Paragraph(_clean_inline(stripped[4:]), styles["h3"])) + i += 1 + continue + + if stripped.startswith("> "): + flow.append(Paragraph(_clean_inline(stripped[2:]), styles["body"])) + i += 1 + continue + + if stripped.startswith("- "): + flow.append(Paragraph("• " + _clean_inline(stripped[2:]), styles["bullet"])) + i += 1 + continue + + if re.match(r"^\d+\.\s", stripped): + flow.append(Paragraph(_clean_inline(stripped), styles["bullet"])) + i += 1 + continue + + para = [stripped] + i += 1 + while i < len(lines): + nxt = lines[i].strip() + if not nxt or nxt.startswith(("#", "|", "-", ">")) or re.match(r"^\d+\.\s", nxt): + break + para.append(nxt) + i += 1 + flow.append(Paragraph(_clean_inline(" ".join(para)), styles["body"])) + + return flow + + +def build_pdf(report_root: Path, output_pdf: Path) -> Path: + styles = _styles() + md_files = _load_order(report_root / "book.md") + + story = [ + Spacer(1, 25 * mm), + Paragraph("B.V. Raman / Parashara / Jaimini Comprehensive Chart Research", styles["title"]), + Paragraph("REDACTED_DATE REDACTED_TIME UTC+8 | REDACTED_PLACE Fengfeng, Hebei | Raman ayanamsa | Mean Node", styles["body"]), + Spacer(1, 12 * mm), + Paragraph("Markdown-first export generated from the report book root.", styles["body"]), + PageBreak(), + ] + + for idx, md_path in enumerate(md_files): + if idx > 0: + story.append(PageBreak()) + story.extend(_render_markdown(md_path, styles)) + + output_pdf.parent.mkdir(parents=True, exist_ok=True) + doc = SimpleDocTemplate( + str(output_pdf), + pagesize=A4, + leftMargin=18 * mm, + rightMargin=18 * mm, + topMargin=18 * mm, + bottomMargin=18 * mm, + title="B.V. Raman / Parashara / Jaimini Comprehensive Chart Research", + author="Codex + repo runtime outputs", + ) + doc.build(story) + return output_pdf + + +def main() -> int: + args = _parse_args() + report_root = Path(args.report_root).resolve() + output_pdf = ( + Path(args.output).resolve() + if args.output + else report_root / "exports" / "chart_research_REDACTED_DATE_REDACTED_TIME.pdf" + ) + build_pdf(report_root, output_pdf) + print(output_pdf) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/historical_event_backtest.py b/scripts/historical_event_backtest.py index b737cbe2..e271ca6a 100644 --- a/scripts/historical_event_backtest.py +++ b/scripts/historical_event_backtest.py @@ -5,8 +5,14 @@ from __future__ import annotations import argparse import json +import sys +from pathlib import Path from typing import Any +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + import mcp_server diff --git a/scripts/interpretation_source_inventory_gate.py b/scripts/interpretation_source_inventory_gate.py index 75c9d30f..4f27209d 100644 --- a/scripts/interpretation_source_inventory_gate.py +++ b/scripts/interpretation_source_inventory_gate.py @@ -180,6 +180,48 @@ def _is_candidate(path: Path) -> bool: def _classify_candidate(path: str, runtime_source_refs: set[str], layer_refs: set[str]) -> dict[str, Any]: + if path in runtime_source_refs and path in { + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/p1_p12.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/house_framework.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/yogas.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/chart_reading_rules.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/validation_rules.md", + }: + return { + "classification": "runtime_reference_layer", + "priority": "runtime", + "promotion_status": "already_wired", + "reason": "Already exposed through interpretation_source_pack.source_refs.", + } + if path.startswith("references/real_case_studies/"): + return { + "classification": "real_case_calibration", + "priority": "priority_1", + "promotion_status": "reference_layer_candidate", + "reason": "Real-case material should be reviewed before confidence calibration claims.", + } + if path.startswith("references/open_source_sources/rishi-ai-mcp/"): + return { + "classification": "open_source_reference", + "priority": "priority_1", + "promotion_status": "already_wired" if path in runtime_source_refs else "reference_layer_candidate", + "reason": "User-prioritized open-source skill/workflow corpus; must remain license-aware.", + } + if path.startswith("references/open_source_sources/vedic-astro-skills/"): + return { + "classification": "open_source_reference", + "priority": "priority_1", + "promotion_status": "already_wired" if path in runtime_source_refs else "reference_layer_candidate", + "reason": "User-prioritized open-source Jyotish skills corpus; classify before selective reuse.", + } + if path.startswith("references/open_source_sources/"): + return { + "classification": "open_source_reference", + "priority": "priority_2", + "promotion_status": "reference_layer_candidate", + "reason": "Open-source reference corpus; classify with license boundary before runtime use.", + } if path in runtime_source_refs: return { "classification": "runtime_reference_layer", @@ -194,34 +236,6 @@ def _classify_candidate(path: str, runtime_source_refs: set[str], layer_refs: se "promotion_status": "indexed", "reason": "Indexed by the interpretation source inventory.", } - if path.startswith("references/real_case_studies/"): - return { - "classification": "real_case_calibration", - "priority": "priority_1", - "promotion_status": "reference_layer_candidate", - "reason": "Real-case material should be reviewed before confidence calibration claims.", - } - if path.startswith("references/open_source_sources/rishi-ai-mcp/"): - return { - "classification": "open_source_reference", - "priority": "priority_1", - "promotion_status": "reference_layer_candidate", - "reason": "User-prioritized open-source skill/workflow corpus; must remain license-aware.", - } - if path.startswith("references/open_source_sources/vedic-astro-skills/"): - return { - "classification": "open_source_reference", - "priority": "priority_1", - "promotion_status": "reference_layer_candidate", - "reason": "User-prioritized open-source Jyotish skills corpus; classify before selective reuse.", - } - if path.startswith("references/open_source_sources/"): - return { - "classification": "open_source_reference", - "priority": "priority_2", - "promotion_status": "reference_layer_candidate", - "reason": "Open-source reference corpus; classify with license boundary before runtime use.", - } if path.startswith("references/oracle/"): return { "classification": "oracle_artifact", diff --git a/scripts/interpretation_source_runtime_coverage.py b/scripts/interpretation_source_runtime_coverage.py new file mode 100644 index 00000000..efb1a78f --- /dev/null +++ b/scripts/interpretation_source_runtime_coverage.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Machine-checkable runtime coverage summary for interpretation sources.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + +PROVEN_RUNTIME_MARKERS = [ + "dasha_timing_layer_used", + "varga_strength_layer_used", + "annual_special_layer_context", + "modifier_obstacle_layer_used", +] + +NOT_FULLY_CLOSED = [ + "references/open_source_sources/jyotishganit", + "references/open_source_sources/jaimini-tropical", + "references/open_source_sources/VedicAstro", + "references/open_source_sources/rishi-ai-mcp", + "references/open_source_sources/vedic-astro-skills", + "references/open_source_sources/dashaflow", +] + + +def build_report() -> dict: + completed = subprocess.run( + [sys.executable, "scripts/interpretation_source_inventory_gate.py"], + cwd=ROOT, + text=True, + capture_output=True, + timeout=120, + check=False, + ) + inventory = {} + if completed.returncode == 0 and completed.stdout.strip(): + inventory = json.loads(completed.stdout) + source_pack_status = ( + inventory.get("source_pack_status") + if isinstance(inventory, dict) + else None + ) or "unknown" + return { + "scope": "interpretation_source_runtime_coverage", + "status": "partial", + "source_pack_status": source_pack_status, + "proven_runtime_markers": PROVEN_RUNTIME_MARKERS, + "runtime_visibility_status": "partial", + "not_fully_closed": NOT_FULLY_CLOSED, + "inventory_gate": { + "status": inventory.get("status") if isinstance(inventory, dict) else "unavailable", + "summary": inventory.get("summary") if isinstance(inventory, dict) else {}, + }, + "boundary": ( + "Inventory/grading exists, but runtime invocation is only proven for surfaced " + "strict-workflow markers, not every local source asset." + ), + } + + +def main() -> int: + print(json.dumps(build_report(), ensure_ascii=False, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/jaimini.py b/scripts/jaimini.py index fe53ff0a..5ab4125b 100644 --- a/scripts/jaimini.py +++ b/scripts/jaimini.py @@ -218,6 +218,31 @@ def calc_upapada(asc_sign_idx: int, planet_longitudes: Dict[str, float]) -> Opti return result +def calc_darapada(asc_sign_idx: int, planet_longitudes: Dict[str, float]) -> Optional[Dict]: + """计算Darapada/A7(第7宫Arudha),用于伴侣外显画像与关系维持审计。""" + seventh_house_idx = (asc_sign_idx + 6) % 12 + result = calc_arudha_pada_for_house(seventh_house_idx, planet_longitudes) + if not result: + return None + result = dict(result) + second_idx = (result['sign_idx'] + 1) % 12 + eighth_idx = (result['sign_idx'] + 7) % 12 + result.update({ + 'house_num': 7, + 'source_house_num': 7, + 'name': 'Darapada (A7)', + 'second_from_a7': _sign_name(second_idx), + 'second_from_a7_lord': SIGN_LORDS[_sign_name(second_idx)], + 'eighth_from_a7': _sign_name(eighth_idx), + 'eighth_from_a7_lord': SIGN_LORDS[_sign_name(eighth_idx)], + 'description': ( + f"A7在{result['sign']},第二宫为{_sign_name(second_idx)}," + f"第八宫为{_sign_name(eighth_idx)},用于伴侣外显画像与关系维持压力审计。" + ), + }) + return result + + def calc_graha_padas(planet_longitudes: Dict[str, float]) -> Dict: """计算行星Graha Pada:行星位置通过其宫主映射出的外显影像。""" results = {} diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index 7f963aac..d80a8e62 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -73,8 +73,10 @@ def execute_consultation_workflow( ) executed_steps = [] known_steps = [ + 'run_prashna', 'compute_chart', 'run_rectification_gate', + 'run_muhurta_panchanga', 'run_historical_event_backtest', 'run_thematic_report', ] @@ -89,16 +91,45 @@ def execute_consultation_workflow( 'executed_steps': [], 'skipped_steps': known_steps, } + result['runtime_evidence_log'] = _UNIFIED_CONSULTATION_ORCHESTRATOR.runtime_evidence_log( + surface=surface, + entry_mode=entry_mode, + route_packet=route_packet, + executed_steps=[], + skipped_steps=known_steps, + blind=bool(body.get('blind') or body.get('blind_technical_mode')), + ) if body.get('return_high_rigor_shape'): result['endpoint'] = 'high_rigor_workflow' return result chart = dict(chart_override) if isinstance(chart_override, dict) else {} + prashna = {} rectification = {} + muhurta_panchanga = {} computed_chart = bool(chart) for step in runtime_planner.get('sync_steps', []): - if step == 'run_rectification_gate': + if step == 'run_prashna': + prashna = handler._compute_prashna({ + **birth_payload, + 'question': body.get('question', 'general'), + 'question_text': body.get('question_text', ''), + 'horary_number': body.get('horary_number'), + 'planets': body.get('planets', {}), + 'asc_degree': body.get('asc_degree', 15.5), + }) + executed_steps.append('run_prashna') + elif step == 'run_muhurta_panchanga': + muhurta_panchanga = handler._compute_muhurta_panchanga({ + **birth_payload, + 'reference_date': body.get('reference_date') or body.get('transit_date') or body.get('today') or body.get('current_date'), + 'question': question, + 'themes': themes, + 'activity': body.get('muhurta_activity'), + }) + executed_steps.append('run_muhurta_panchanga') + elif step == 'run_rectification_gate': chart_planets = chart.get('planets') if isinstance(chart, dict) else {} chart_ascendant = chart.get('ascendant') if isinstance(chart, dict) else {} rectification = handler._compute_rectification_gate({ @@ -121,6 +152,8 @@ def execute_consultation_workflow( executed_steps.append('run_historical_event_backtest') chart_for_theme = dict(chart) if isinstance(chart, dict) else {} + if entry_mode == 'prashna' and isinstance(prashna, dict): + chart_for_theme.setdefault('prashna', prashna) if isinstance(chart_for_theme.get('modules'), dict): chart_for_theme.update(chart_for_theme.get('modules', {}).get('chart') or {}) @@ -128,6 +161,7 @@ def execute_consultation_workflow( prompt_snapshot = (((chart.get('ai_prompt_pack') or {}).get('evidence_snapshot')) or {}) if isinstance(chart, dict) else {} strict_workflow_contracts = prompt_snapshot.get('strict_workflow_contracts') if isinstance(prompt_snapshot.get('strict_workflow_contracts'), dict) else {} chart_guided_topics = modules.get('guided_topics') if isinstance(modules.get('guided_topics'), list) else [] + audited_remedies = handler._build_audited_remedies_from_guided_topics(chart_guided_topics) thematic_report = {} if 'run_thematic_report' in runtime_planner.get('sync_steps', []): @@ -149,7 +183,30 @@ def execute_consultation_workflow( executed_steps.append('run_thematic_report') vedastro_official = handler._high_rigor_vedastro_official_summary(chart) + runtime_truth = vedastro_official.get('runtime_truth') if isinstance(vedastro_official.get('runtime_truth'), dict) else {} + interpretation_source_runtime_coverage = handler._interpretation_source_runtime_coverage(chart) skipped_steps = [step for step in known_steps if step not in executed_steps] + machine_evidence_packet = _UNIFIED_CONSULTATION_ORCHESTRATOR.machine_evidence_packet( + chart=chart, + route_packet=route_packet, + vedastro_official=vedastro_official, + ) + real_case_calibration = _UNIFIED_CONSULTATION_ORCHESTRATOR.real_case_calibration_catalog( + route_packet=route_packet, + machine_evidence_packet=machine_evidence_packet, + ) + runtime_evidence_log = _UNIFIED_CONSULTATION_ORCHESTRATOR.runtime_evidence_log( + surface=surface, + entry_mode=entry_mode, + route_packet=route_packet, + executed_steps=executed_steps, + skipped_steps=skipped_steps, + vedastro_official=vedastro_official, + interpretation_source_runtime_coverage=interpretation_source_runtime_coverage, + machine_evidence_packet=machine_evidence_packet, + real_case_calibration=real_case_calibration, + blind=bool(body.get('blind') or body.get('blind_technical_mode')), + ) result = { 'success': True, @@ -187,7 +244,15 @@ def execute_consultation_workflow( 'rectification': rectification, 'historical_event_backtest': historical_backtest, 'thematic_report': thematic_report, + 'prashna': prashna, + 'muhurta_panchanga': muhurta_panchanga, + 'audited_remedies': audited_remedies, 'vedastro_official': vedastro_official, + 'runtime_truth': runtime_truth, + 'interpretation_source_runtime_coverage': interpretation_source_runtime_coverage, + 'machine_evidence_packet': machine_evidence_packet, + 'real_case_calibration': real_case_calibration, + 'runtime_evidence_log': runtime_evidence_log, 'next_questions': handler._high_rigor_next_questions(rectification, historical_backtest), 'boundary': ( 'This endpoint composes existing project workflows. It does not claim that every VedAstro callable ' @@ -244,6 +309,15 @@ def _api_chart_cache_ttl_seconds() -> float: return max(ttl, 0.0) +def _free_tier_queue_enabled_env() -> bool: + raw_values = [ + str(os.environ.get("VEDASTRO_FREE_TIER_QUEUE", "")).strip().lower(), + str(os.environ.get("VEDASTRO_FREE_TIER_QUEUE_ENABLED", "")).strip().lower(), + str(os.environ.get("VEDASTRO_ENABLE_FREE_TIER_QUEUE", "")).strip().lower(), + ] + return any(value in {"1", "true", "yes", "on"} for value in raw_values) + + def _vedastro_runtime_fingerprint() -> dict: endpoint = os.environ.get('VEDASTRO_API_ENDPOINT', '').strip() return { @@ -1623,6 +1697,47 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): surface=body.get('surface', 'api_web'), ) + def _build_audited_remedies_from_guided_topics(self, guided_topics): + if not isinstance(guided_topics, list): + return {'status': 'blocked', 'reason': 'guided_topics_missing'} + selected_gate = None + selected_topic = None + for topic in guided_topics: + if not isinstance(topic, dict): + continue + gate = topic.get('strict_audit_gate') + if isinstance(gate, dict): + selected_gate = gate + selected_topic = topic + break + if not isinstance(selected_gate, dict): + return {'status': 'blocked', 'reason': 'strict_audit_gate_missing'} + strength_context = selected_gate.get('strength_context') if isinstance(selected_gate.get('strength_context'), dict) else {} + dosha_context = selected_gate.get('dosha_context') if isinstance(selected_gate.get('dosha_context'), list) else [] + active_dasha_lord = selected_gate.get('active_dasha_lord') if isinstance(selected_gate.get('active_dasha_lord'), str) else '' + if not strength_context: + return {'status': 'blocked', 'reason': 'strength_context_missing', 'source': 'strict_audit_gate'} + payload = self._compute_remedies({ + 'shadbala': strength_context, + 'doshas': dosha_context, + 'dasha_lord': active_dasha_lord, + }) + if not isinstance(payload, dict): + payload = {'recommendations': payload} + return { + 'status': 'ok', + 'source': 'strict_audit_gate', + 'topic': ( + selected_gate.get('topic') + or selected_topic.get('id') + or selected_topic.get('title') + or 'general' + ), + 'active_dasha_lord': active_dasha_lord, + 'recommendations': payload.get('recommendations') or payload, + 'raw': payload, + } + def _compute_high_rigor_workflow_sync(self, body): body_copy = dict(body or {}) body_copy.pop('async', None) @@ -1969,6 +2084,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): if not isinstance(prompt_full_snapshot, dict): prompt_full_snapshot = {} modules = chart.get('modules') if isinstance(chart, dict) else {} + if not isinstance(modules, dict): + modules = {} range_scan = modules.get('vedastro_range_scan_result') if isinstance(modules, dict) else {} if not isinstance(range_scan, dict): range_scan = {} @@ -1976,6 +2093,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): official_snapshot = range_scan.get('official_full_snapshot') if isinstance(range_scan, dict) else {} if not isinstance(official_snapshot, dict): official_snapshot = {} + if not official_snapshot and isinstance(modules.get('vedastro_official_full_snapshot'), dict): + official_snapshot = modules.get('vedastro_official_full_snapshot') or {} metadata = official_snapshot.get('source_metadata') if isinstance(official_snapshot, dict) else {} catalog = metadata.get('official_full_capability_catalog') if isinstance(metadata, dict) else {} if not isinstance(catalog, dict): @@ -2024,13 +2143,94 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): if isinstance(selection, dict) and isinstance(selection.get('report_reference'), dict) } ) - return { - 'status': ( - prompt_official.get('status') - or official_snapshot.get('status') - or range_scan.get('status') - or 'blocked' + status = ( + prompt_official.get('status') + or official_snapshot.get('status') + or range_scan.get('status') + or 'blocked' + ) + chart_core_status = 'blocked' + official_primary_evidence = ( + primary_contract.get('official_primary_evidence') + or prompt_official.get('official_primary_evidence') + or {} + ) + if not isinstance(official_primary_evidence, dict): + official_primary_evidence = {} + chart_core = official_primary_evidence.get('chart_core') + if isinstance(chart_core, dict) and chart_core.get('status'): + chart_core_status = chart_core.get('status') + elif full_snapshot_payload.get('available'): + chart_core_status = 'ok' + event_radar_status = 'blocked' + if ( + prompt_official.get('blocked_items') + or prompt_official.get('fallback_used') + or prompt_official.get('conflicts') + ): + event_radar_status = 'partial' + elif range_scan.get('status') == 'ok': + event_radar_status = 'ok' + elif range_scan.get('status'): + event_radar_status = 'partial' + runtime_truth = { + 'status': status, + 'catalog_boundary': 'catalog_recognized_not_full_runtime_execution', + 'primary_route': strict_workflow_primary_route or _selected_route, + 'routes_available': strict_workflow_routes_available, + 'official_execution_layers': { + 'chart_core': chart_core_status, + 'event_radar': event_radar_status, + 'catalog_status': ( + prompt_official.get('official_full_capability_catalog_status') + or catalog.get('status') + or range_metadata.get('official_full_capability_catalog_status') + or official_snapshot.get('status') + or 'blocked' + ), + }, + 'fallback_active': bool( + primary_contract.get('fallback_used') + or prompt_official.get('fallback_used') ), + 'blocked_items': ( + primary_contract.get('blocked_items') + or prompt_official.get('blocked_items') + or [] + ), + 'conflicts': ( + primary_contract.get('conflicts') + or prompt_official.get('conflicts') + or [] + ), + 'free_tier_strategy': { + 'using_free_tier': not bool(os.environ.get('VEDASTRO_API_KEY', '').strip()), + 'queue_enabled': _free_tier_queue_enabled_env(), + 'cache_hit': bool( + (((official_snapshot.get('source_metadata') or {}).get('semantic_cache') or {}).get('cache_hit')) + if isinstance(official_snapshot, dict) + else False + ), + 'guard_status': ( + 'degraded_or_partial' + if status in {'partial', 'blocked', 'official_snapshot_budget_exhausted'} + or bool(prompt_official.get('blocked_items')) + else 'within_free_tier_strategy' + ), + }, + } + raw_response = ( + official_snapshot.get('raw_response') + or official_snapshot.get('official_raw_response') + or official_snapshot.get('raw_payload') + or official_snapshot.get('raw') + or prompt_full_snapshot.get('raw_response') + or prompt_full_snapshot.get('official_raw_response') + or prompt_full_snapshot.get('raw_payload') + or prompt_full_snapshot.get('raw') + ) + return { + 'status': status, 'range_scan_status': range_scan.get('status') if isinstance(range_scan, dict) else None, 'event_count': int(range_scan.get('event_count', 0) or 0) if isinstance(range_scan, dict) else 0, 'official_full_capability_catalog_status': ( @@ -2090,12 +2290,58 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): 'verdict': primary_contract.get('verdict'), 'dominant_label': primary_contract.get('dominant_label'), 'main_conflicts': primary_contract.get('main_conflicts') or primary_contract.get('conflicts') or [], + 'runtime_truth': runtime_truth, + 'raw_response': raw_response, 'boundary': 'VedAstro official snapshot and capability catalog are consumed as primary evidence metadata; execution breadth depends on configured network and sample limits.', } + def _interpretation_source_runtime_coverage(self, chart): + modules = chart.get('modules') if isinstance(chart, dict) else {} + if not isinstance(modules, dict): + modules = {} + prompt_pack = chart.get('ai_prompt_pack') if isinstance(chart, dict) else {} + evidence_snapshot = prompt_pack.get('evidence_snapshot') if isinstance(prompt_pack, dict) else {} + interpretation_pack = evidence_snapshot.get('interpretation_source_pack') if isinstance(evidence_snapshot.get('interpretation_source_pack'), dict) else {} + candidates = { + 'dasha_timing_layer_used', + 'varga_strength_layer_used', + 'annual_special_layer_context', + 'modifier_obstacle_layer_used', + } + proven_markers = [] + guided_topics = modules.get('guided_topics') if isinstance(modules.get('guided_topics'), list) else [] + for topic in guided_topics: + if not isinstance(topic, dict): + continue + strict_gate = topic.get('strict_audit_gate') + if not isinstance(strict_gate, dict): + continue + secondary = strict_gate.get('secondary_context') + if not isinstance(secondary, list): + continue + for item in secondary: + if isinstance(item, str) and item in candidates and item not in proven_markers: + proven_markers.append(item) + return { + 'source_pack_status': interpretation_pack.get('status') or 'used', + 'proven_runtime_markers': proven_markers, + 'runtime_visibility_status': 'partial' if proven_markers else 'blocked', + 'not_fully_closed': [ + 'references/open_source_sources/jyotishganit', + 'references/open_source_sources/jaimini-tropical', + 'references/open_source_sources/VedicAstro', + 'references/open_source_sources/rishi-ai-mcp', + 'references/open_source_sources/vedic-astro-skills', + 'references/open_source_sources/dashaflow', + ], + 'boundary': 'Inventory/grading exists, but full runtime invocation is only proven for surfaced strict-workflow markers, not every local source asset.', + } + def _high_rigor_next_questions(self, rectification, historical_backtest): questions = [] summary = rectification.get('summary') if isinstance(rectification, dict) else {} + if not isinstance(summary, dict): + summary = {} for item in summary.get('recommended_events') or []: questions.append({ 'type': 'yes_no_or_date', @@ -5424,6 +5670,42 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): raise BadRequest(str(e)) from e return {'success': True, 'endpoint': 'panchanga_range', 'report': report} + def _compute_muhurta_panchanga(self, body): + reference_date = body.get('reference_date') or body.get('transit_date') or body.get('today') or datetime.now().strftime('%Y-%m-%d') + if not isinstance(reference_date, str): + raise BadRequest('reference_date must be a string') + date_str = reference_date[:10] + try: + datetime.strptime(date_str, '%Y-%m-%d') + except ValueError as e: + raise BadRequest('reference_date must be YYYY-MM-DD') from e + raw_activity = body.get('activity') + if raw_activity is not None and not isinstance(raw_activity, str): + raise BadRequest('activity must be a string') + activity = (raw_activity or '').strip().lower() + question_text = str(body.get('question') or '') + themes = body.get('themes') if isinstance(body.get('themes'), list) else [] + if activity not in {'marriage', 'business', 'travel', 'medical', 'education'}: + if 'marriage' in themes or any(token in question_text for token in ('婚', '恋', 'marry', 'wedding', 'relationship')): + activity = 'marriage' + elif any(token in question_text for token in ('travel', '迁移', '搬家', '出行')): + activity = 'travel' + elif any(token in question_text for token in ('medical', '手术', '治疗', '健康')): + activity = 'medical' + elif any(token in question_text for token in ('education', '学习', '考试', '申请')): + activity = 'education' + else: + activity = 'business' + muhurta = _load_local_module('muhurta') + return muhurta.build_muhurta_sidecar( + date_str=date_str, + activity=activity, + lat=self._get_float(body, 'lat', 0, -90, 90), + lon=self._get_float(body, 'lon', 0, -180, 180), + tz=self._get_float(body, 'tz', 0, -14, 14), + ayanamsa_name=body.get('ayanamsa', 'lahiri'), + ) + def _compute_rectification_gate(self, body): asc_lon = self._asc_lon_from_body(body) declared_accuracy = body.get('declared_accuracy', body.get('accuracy', 'minute')) diff --git a/scripts/kp_system.py b/scripts/kp_system.py index 59fa3e5d..c1e4cf96 100644 --- a/scripts/kp_system.py +++ b/scripts/kp_system.py @@ -10,6 +10,7 @@ KP (Krishnamurti Paddhati) 占星系统模块 3. House Significator ABCD体系 """ +from datetime import datetime, timedelta from typing import Dict, List, Tuple, Optional SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', @@ -33,6 +34,7 @@ NAKSHATRAS = [ VIMSHOTTARI_DURATION = [7, 20, 6, 10, 7, 18, 16, 19, 17] KP_LORDS = ["Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury"] STAR_LORDS = KP_LORDS * 3 # 27 Nakshatras = 3 cycles of 9 lords +VIMSHOTTARI_YEARS = dict(zip(KP_LORDS, VIMSHOTTARI_DURATION)) NAKSHATRA_SPAN = 360.0 / 27.0 # 13.333... degrees @@ -258,3 +260,102 @@ def calc_kp_analysis(planet_positions: Dict, asc_sign: str = 'Aries') -> Dict: 'houses': {h['house']: {'sign': h['sign'], 'kp_lords': h['kp_lords'], 'significators': house_sig.get(h['house'], {})} for h in houses}, } + + +def _kp_next_lords(start_lord: str) -> List[str]: + idx = KP_LORDS.index(start_lord) + return KP_LORDS[idx:] + KP_LORDS[:idx] + + +def _kp_years_to_days(years: float) -> float: + return years * 365.2425 + + +def _kp_birth_star_balance(moon_longitude: float) -> Tuple[str, float]: + moon_longitude = moon_longitude % 360.0 + nak_idx = int(moon_longitude // NAKSHATRA_SPAN) % 27 + star_lord = STAR_LORDS[nak_idx] + elapsed = (moon_longitude % NAKSHATRA_SPAN) / NAKSHATRA_SPAN + return star_lord, max(0.0, min(1.0, 1.0 - elapsed)) + + +def _kp_period_score(lords: List[str], planet_house_significators: Optional[Dict[str, Dict]] = None) -> Dict: + supportive_houses = {2, 5, 7, 11} + blocking_houses = {1, 6, 8, 10, 12} + supportive = 0 + blocking = 0 + details = {} + for lord in lords: + sig = (planet_house_significators or {}).get(lord, {}) + houses = set() + for value in sig.values(): + if isinstance(value, int): + houses.add(value) + elif isinstance(value, list): + houses.update(v for v in value if isinstance(v, int)) + support_hits = sorted(houses & supportive_houses) + block_hits = sorted(houses & blocking_houses) + supportive += len(support_hits) + blocking += len(block_hits) + details[lord] = {'supportive_houses': support_hits, 'blocking_houses': block_hits} + score = supportive - blocking + if score >= 2: + judgement = 'supportive' + elif score <= -2: + judgement = 'blocking' + else: + judgement = 'mixed' + return { + 'marriage_score': score, + 'supportive_hits': supportive, + 'blocking_hits': blocking, + 'judgement': judgement, + 'lord_details': details, + } + + +def calc_kp_dba_timeline( + birth_datetime: datetime, + moon_longitude: float, + target_start: datetime, + target_end: datetime, + planet_house_significators: Optional[Dict[str, Dict]] = None, +) -> Dict: + """Build Vimshottari MD/AD/PD windows for KP-style marriage timing review.""" + birth_star_lord, balance = _kp_birth_star_balance(moon_longitude) + periods = [] + md_start = birth_datetime + for md_i, md_lord in enumerate(_kp_next_lords(birth_star_lord) * 3): + md_years = VIMSHOTTARI_YEARS[md_lord] * (balance if md_i == 0 else 1.0) + md_end = md_start + timedelta(days=_kp_years_to_days(md_years)) + ad_start = md_start + for ad_lord in _kp_next_lords(md_lord): + ad_years = md_years * VIMSHOTTARI_YEARS[ad_lord] / 120.0 + ad_end = ad_start + timedelta(days=_kp_years_to_days(ad_years)) + pd_start = ad_start + for pd_lord in _kp_next_lords(ad_lord): + pd_years = ad_years * VIMSHOTTARI_YEARS[pd_lord] / 120.0 + pd_end = pd_start + timedelta(days=_kp_years_to_days(pd_years)) + if pd_end >= target_start and pd_start <= target_end: + scored = _kp_period_score([md_lord, ad_lord, pd_lord], planet_house_significators) + periods.append({ + 'md_lord': md_lord, + 'ad_lord': ad_lord, + 'pd_lord': pd_lord, + 'start': pd_start.isoformat(), + 'end': pd_end.isoformat(), + **scored, + }) + pd_start = pd_end + ad_start = ad_end + md_start = md_end + if md_start > target_end: + break + return { + 'method': 'KP DBA timeline (Vimshottari MD/AD/PD)', + 'birth_star_lord': birth_star_lord, + 'birth_star_balance_fraction': balance, + 'target_start': target_start.isoformat(), + 'target_end': target_end.isoformat(), + 'periods': periods, + } diff --git a/scripts/muhurta.py b/scripts/muhurta.py index a5069e3c..3fff9fc5 100644 --- a/scripts/muhurta.py +++ b/scripts/muhurta.py @@ -1507,6 +1507,60 @@ def muhurta_range_search( } +def build_muhurta_sidecar( + *, + date_str: str, + activity: str = 'business', + lat: Optional[float] = None, + lon: Optional[float] = None, + tz: Optional[float] = None, + ayanamsa_name: str = 'lahiri', +) -> Dict: + """Compact Muhurta/Panchanga packet for unified workflow consumers.""" + activity = activity if activity in ACTIVITY_RULES else 'business' + search = muhurta_range_search( + date_str, + date_str, + activity=activity, + limit=3, + lat=lat, + lon=lon, + tz=tz, + ayanamsa_name=ayanamsa_name, + ) + calendar = panchanga_range_report( + date_str, + date_str, + activity=activity, + lat=lat, + lon=lon, + tz=tz, + ayanamsa_name=ayanamsa_name, + ) + days = calendar.get('days') or [] + first_day = days[0] if days else {} + return { + 'status': 'ok', + 'source': 'local_muhurta.py', + 'date': date_str, + 'activity': activity, + 'activity_label': ACTIVITY_RULES[activity]['name'], + 'report_mode': search.get('mode', 'muhurta_date_range_solver'), + 'panchanga': { + 'query_date': first_day.get('query_date', date_str), + 'summary': first_day.get('summary') or {}, + 'panchanga': first_day.get('panchanga') or {}, + 'inauspicious_periods': first_day.get('inauspicious_periods') or [], + 'choghadiya': first_day.get('choghadiya') or [], + 'hora_windows': first_day.get('hora_windows') or [], + 'condition_tags': first_day.get('condition_tags') or [], + }, + 'best_windows': search.get('best_windows') or [], + 'calculation_policy': calendar.get('calculation_policy') or search.get('calculation_policy') or {}, + 'next_action': search.get('next_action') or 'Use as timing sidecar only; final judgement still needs chart-based workflow.', + } + + def _build_muhurta_candidate(row: Dict, activity: str, avoid_inauspicious_periods: bool) -> Dict: summary = row.get('summary') or {} panchanga = row.get('panchanga') or {} diff --git a/scripts/pre_work_check.py b/scripts/pre_work_check.py new file mode 100644 index 00000000..6185032c --- /dev/null +++ b/scripts/pre_work_check.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""One-command pre-work governance check. + +Runs the lightweight guardrails that should happen before substantial work: +ledger/docs presence, git status visibility, fragment scan, remote visibility, +and focused governance tests. +""" + +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +PYTHON = sys.executable +DEFAULT_COMMAND_TIMEOUT_SECONDS = 45 +DEFAULT_FRAGMENT_TIMEOUT_SECONDS = 90 +FOCUSED_TEST_TARGETS = [ + "tests/test_runtime_import_boundaries.py", + "tests/test_project_fragment_governance.py", + "tests/test_preflight_fragment_scan.py", + "tests/test_remote_repo_visibility_check.py", + "tests/test_pre_work_check.py", +] +EXTERNAL_ENGINE_DIAGNOSTIC_TARGET = "scripts/diagnose_external_engine_adapters.py" +PRE_WORK_DOCS = [ + "AGENTS.md", + "docs/research/pre_work_error_ledger.md", + "docs/research/whole_machine_fragment_sweep_2026_07_05.md", + "docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md", +] + + +def run(args: list[str], timeout: int, env: dict[str, str] | None = None) -> dict[str, Any]: + try: + completed = subprocess.run( + args, + cwd=ROOT, + text=True, + capture_output=True, + timeout=timeout, + check=False, + env=env, + ) + except subprocess.TimeoutExpired as exc: + return { + "ok": False, + "returncode": None, + "stdout": exc.stdout or "", + "stderr": exc.stderr or "", + "error": f"timeout after {timeout}s", + } + return { + "ok": completed.returncode == 0, + "returncode": completed.returncode, + "stdout": completed.stdout, + "stderr": completed.stderr, + "error": "" if completed.returncode == 0 else (completed.stderr or completed.stdout).strip(), + } + + +def classify_status( + docs_ok: bool, + fragment_ok: bool, + pytest_ok: bool, + remote_status: str, + external_engine_ok: bool = True, +) -> str: + if not docs_ok or not fragment_ok or not pytest_ok or not external_engine_ok: + return "fail" + if remote_status == "verified": + return "pass" + return "pass_with_remote_blocked" + + +def build_report(remote_timeout: int, command_timeout: int, fragment_timeout: int, skip_tests: bool = False) -> dict[str, Any]: + docs = {path: (ROOT / path).exists() for path in PRE_WORK_DOCS} + git_status = run(["git", "status", "--short", "--branch"], command_timeout) + git_remote = run(["git", "remote", "-v"], command_timeout) + fragment = run([PYTHON, "scripts/preflight_fragment_scan.py"], fragment_timeout) + external_engine = run([PYTHON, EXTERNAL_ENGINE_DIAGNOSTIC_TARGET, "--json"], command_timeout) + remote = run([PYTHON, "scripts/remote_repo_visibility_check.py", "--timeout", str(remote_timeout)], command_timeout) + remote_report: dict[str, Any] = {} + if remote["ok"]: + try: + remote_report = json.loads(remote["stdout"]) + except json.JSONDecodeError as exc: + remote_report = {"status": "blocked", "must_not_claim_synced": True, "parse_error": str(exc)} + pytest_result = {"ok": True, "stdout": "skipped", "stderr": "", "error": ""} + if not skip_tests: + env = dict(os.environ) + if fragment["ok"] and fragment.get("stdout"): + cache = Path(tempfile.gettempdir()) / "jyotish_preflight_fragment_scan_report.json" + cache.write_text(fragment["stdout"], encoding="utf-8") + env["PREFLIGHT_FRAGMENT_SCAN_REPORT"] = str(cache) + pytest_result = run([PYTHON, "-m", "pytest", "-q", *FOCUSED_TEST_TARGETS], command_timeout, env=env) + remote_status = str(remote_report.get("status") or "blocked") + status = classify_status( + docs_ok=all(docs.values()), + fragment_ok=fragment["ok"], + pytest_ok=pytest_result["ok"], + remote_status=remote_status, + external_engine_ok=external_engine["ok"], + ) + return { + "scope": "pre_work_check", + "status": status, + "must_not_claim_synced": remote_report.get("must_not_claim_synced", True), + "docs": docs, + "git": { + "status_ok": git_status["ok"], + "status": git_status["stdout"], + "remote_ok": git_remote["ok"], + "remote": git_remote["stdout"], + }, + "checks": { + "fragment_scan_ok": fragment["ok"], + "external_engine_adapters_ok": external_engine["ok"], + "remote_visibility_status": remote_status, + "remote_visibility_ok": remote["ok"], + "focused_tests_ok": pytest_result["ok"], + }, + "errors": { + "fragment_scan": fragment["error"], + "external_engine_adapters": external_engine["error"], + "remote_visibility": remote["error"], + "focused_tests": pytest_result["error"], + }, + "focused_test_targets": FOCUSED_TEST_TARGETS, + "external_engine_diagnostic_target": EXTERNAL_ENGINE_DIAGNOSTIC_TARGET, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--remote-timeout", type=int, default=8) + parser.add_argument("--command-timeout", type=int, default=DEFAULT_COMMAND_TIMEOUT_SECONDS) + parser.add_argument("--fragment-timeout", type=int, default=DEFAULT_FRAGMENT_TIMEOUT_SECONDS) + parser.add_argument("--skip-tests", action="store_true") + args = parser.parse_args() + report = build_report(args.remote_timeout, args.command_timeout, args.fragment_timeout, args.skip_tests) + print(json.dumps(report, ensure_ascii=False, indent=2)) + return 1 if report["status"] == "fail" else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/preflight_fragment_scan.py b/scripts/preflight_fragment_scan.py index cfa63108..b34f85c0 100644 --- a/scripts/preflight_fragment_scan.py +++ b/scripts/preflight_fragment_scan.py @@ -31,6 +31,9 @@ EXTERNAL_WORK_BRAIN_DIR = Path("/Users/wuyongnaren/.gemini/antigravity-ide/brain DISTRIBUTION_MIRROR_DIR = Path("/Users/wuyongnaren/.workbuddy/skills/jyotish-vedic-astrology") ORACLE_FILE = ROOT / "references" / "oracle" / "dasha_shadbala_oracle_cases.json" REPO_CLEANUP_MAP = ROOT / "docs" / "research" / "repo_cleanup_promotion_map_2026_07_01.md" +ERROR_LEDGER = ROOT / "docs" / "research" / "pre_work_error_ledger.md" +LATEST_SWEEP = ROOT / "docs" / "research" / "whole_machine_fragment_sweep_2026_07_05.md" +ROUND25_SWEEP = ROOT / "docs" / "research" / "whole_machine_fragment_sweep_round25_2026_06_25.md" def _read_text(path: Path) -> str: @@ -300,6 +303,26 @@ def build_report() -> dict[str, Any]: "/Users/wuyongnaren/.workbuddy/skills/jyotish-vedic-astrology", ], }, + "governance": { + "pre_work_error_ledger": { + "path": str(ERROR_LEDGER), + "exists": ERROR_LEDGER.exists(), + }, + "latest_fragment_sweep": { + "path": str(LATEST_SWEEP), + "exists": LATEST_SWEEP.exists(), + "remote_ref_parity": "blocked_until_git_ls_remote_succeeds", + }, + "prior_fragment_sweep": { + "path": str(ROUND25_SWEEP), + "exists": ROUND25_SWEEP.exists(), + }, + "acceptance_command": ( + "python3 -m pytest -q tests/test_runtime_import_boundaries.py " + "tests/test_project_fragment_governance.py tests/test_preflight_fragment_scan.py " + "tests/test_remote_repo_visibility_check.py tests/test_pre_work_check.py" + ), + }, "boundary": ( "Run this preflight scan before major work so drafts, mirrors, and external-work-brain " "fragments are reviewed deliberately, and so engineering-surface success is not mistaken " diff --git a/scripts/print_cline_mcp_config.py b/scripts/print_cline_mcp_config.py new file mode 100644 index 00000000..588180f7 --- /dev/null +++ b/scripts/print_cline_mcp_config.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Print or install Cline MCP config for this Jyotish repo.""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def build_config(repo_root: Path, python_bin: str) -> dict: + repo_root = repo_root.resolve() + return { + "mcpServers": { + "jyotish": { + "command": python_bin, + "args": [str(repo_root / "mcp_server.py")], + "cwd": str(repo_root), + "env": { + "PYTHONPATH": str(repo_root / "scripts"), + }, + } + } + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo-root", default=str(ROOT), help="repo root for generated paths") + parser.add_argument("--python", default=sys.executable, help="Python executable for Cline to run") + parser.add_argument( + "--install-project", + action="store_true", + help="write project-local .cline/mcp.json for this checkout", + ) + args = parser.parse_args() + repo_root = Path(args.repo_root).resolve() + config = build_config(repo_root, args.python) + text = json.dumps(config, ensure_ascii=False, indent=2) + if args.install_project: + target = repo_root / ".cline" / "mcp.json" + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(text + "\n", encoding="utf-8") + print(str(target)) + return 0 + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) + diff --git a/scripts/remote_repo_visibility_check.py b/scripts/remote_repo_visibility_check.py new file mode 100644 index 00000000..c970e87e --- /dev/null +++ b/scripts/remote_repo_visibility_check.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Read-only remote repository visibility diagnostic. + +This is a guardrail, not a sync tool. It never pushes, fetches, or mutates the +worktree. It records whether terminal git can verify remote refs and whether +GitHub's API is reachable as a fallback visibility signal. +""" + +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from pathlib import Path +from typing import Any +from urllib import request + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_GITHUB_SLUG = "732642856/yinduzhanxing" + + +def run_command(args: list[str], timeout: int) -> dict[str, Any]: + try: + completed = subprocess.run( + args, + cwd=ROOT, + text=True, + capture_output=True, + timeout=timeout, + check=False, + ) + except subprocess.TimeoutExpired as exc: + return {"ok": False, "error": f"timeout after {timeout}s: {' '.join(args)}", "stdout": exc.stdout or "", "stderr": exc.stderr or ""} + return { + "ok": completed.returncode == 0, + "returncode": completed.returncode, + "stdout": completed.stdout, + "stderr": completed.stderr, + "error": "" if completed.returncode == 0 else (completed.stderr or completed.stdout).strip(), + } + + +def github_slug_from_remote_url(url: str) -> str | None: + patterns = [ + r"github\.com[:/](?P[^/]+)/(?P[^/.]+)(?:\.git)?$", + r"github\.com/(?P[^/]+)/(?P[^/.]+)(?:\.git)?$", + ] + for pattern in patterns: + match = re.search(pattern, url.strip()) + if match: + return f"{match.group('owner')}/{match.group('repo')}" + return None + + +def parse_ls_remote(stdout: str) -> dict[str, Any]: + heads: dict[str, str] = {} + tags: dict[str, str] = {} + for line in stdout.splitlines(): + parts = line.split() + if len(parts) != 2: + continue + sha, ref = parts + if ref.startswith("refs/heads/"): + heads[ref.removeprefix("refs/heads/")] = sha + elif ref.startswith("refs/tags/") and not ref.endswith("^{}"): + tags[ref.removeprefix("refs/tags/")] = sha + return {"heads": heads, "tags": tags, "ref_count": len(heads) + len(tags)} + + +def git_remote_urls(timeout: int) -> list[str]: + urls: list[str] = [] + for args in (["git", "remote", "get-url", "origin"], ["git", "remote", "get-url", "--push", "origin"]): + result = run_command(args, timeout) + if result["ok"]: + url = result["stdout"].strip() + if url and url not in urls: + urls.append(url) + return urls + + +def git_ls_remote(url: str, timeout: int) -> dict[str, Any]: + result = run_command(["git", "ls-remote", "--heads", "--tags", url], timeout) + parsed = parse_ls_remote(result.get("stdout", "")) if result["ok"] else {"heads": {}, "tags": {}, "ref_count": 0} + return {"method": "git_ls_remote", "url": url, **parsed, "ok": result["ok"], "error": result.get("error", "")} + + +def github_api_branches(slug: str, timeout: int) -> dict[str, Any]: + url = f"https://api.github.com/repos/{slug}/branches" + req = request.Request(url, headers={"User-Agent": "jyotish-preflight/1.0", "Accept": "application/vnd.github+json"}) + try: + with request.urlopen(req, timeout=timeout) as response: + payload = json.loads(response.read().decode("utf-8")) + except Exception as exc: # network/SSL/status errors are diagnostic data here + return {"method": "github_api_branches", "url": url, "ok": False, "branches": [], "error": f"{type(exc).__name__}: {exc}"} + branches = [item.get("name", "") for item in payload if isinstance(item, dict)] + return {"method": "github_api_branches", "url": url, "ok": bool(branches), "branches": branches, "error": ""} + + +def build_report(timeout: int) -> dict[str, Any]: + local_branch = run_command(["git", "rev-parse", "--abbrev-ref", "HEAD"], timeout) + local_head = run_command(["git", "rev-parse", "HEAD"], timeout) + urls = git_remote_urls(timeout) + slug = next((github_slug_from_remote_url(url) for url in urls if github_slug_from_remote_url(url)), DEFAULT_GITHUB_SLUG) + git_checks = [git_ls_remote(url, timeout) for url in urls] or [git_ls_remote(f"https://github.com/{slug}.git", timeout)] + api_check = github_api_branches(slug, timeout) + git_verified = any(check["ok"] and check["ref_count"] > 0 for check in git_checks) + api_visible = api_check["ok"] + status = "verified" if git_verified else ("web_visible_git_blocked" if api_visible else "blocked") + return { + "scope": "remote_repo_visibility_check", + "status": status, + "must_not_claim_synced": status != "verified", + "local": { + "branch": local_branch.get("stdout", "").strip(), + "head": local_head.get("stdout", "").strip(), + }, + "github_slug": slug, + "git_checks": git_checks, + "github_api": api_check, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--timeout", type=int, default=12) + args = parser.parse_args() + print(json.dumps(build_report(args.timeout), ensure_ascii=False, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_quality_gate.py b/scripts/run_quality_gate.py index fdaa8529..698f6adb 100644 --- a/scripts/run_quality_gate.py +++ b/scripts/run_quality_gate.py @@ -35,6 +35,7 @@ EXTRA_COMPILE_TARGETS = [ ROOT / "scripts" / "oracle_boundary_audit.py", ROOT / "scripts" / "oracle_collection_queue.py", ROOT / "scripts" / "oracle_evidence_validator.py", + ROOT / "scripts" / "sync_final_evidence_packet_status.py", ROOT / "scripts" / "deployment_preflight.py", ROOT / "tests" / "run_golden_cases.py", ROOT / "tests" / "run_real_case_revalidation.py", @@ -53,6 +54,17 @@ CORE_PYTEST_TARGETS = [ "tests/test_external_oracle_sanity_closure.py", ] +RUNTIME_TRUTH_PYTEST_TARGETS = [ + "tests/test_api_server_security.py::test_high_rigor_vedastro_official_summary_passes_through_contract_fields", + "tests/test_api_server_security.py::test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_full_snapshot", + "tests/test_vedastro_external_technique_evidence.py::test_strict_workflow_uses_shared_consultation_executor", + "tests/test_vedastro_runtime_mode_diagnostics.py", + "tests/test_interpretation_source_inventory_gate.py::test_quality_gate_runs_interpretation_source_inventory_gate", + "tests/test_interpretation_source_runtime_coverage.py", + "tests/test_final_jhora_evidence_packet_acceptance.py", + "tests/test_frontend_productization.py::test_result_page_surfaces_workflow_summary_and_provenance_detail", +] + RELEASE_CRITICAL_UNTRACKED_PATHS = [ "docs/research/desktop_packaging_spike_2026_06_23.md", "docs/research/ephemeris_abstraction_feasibility_2026_06_23.md", @@ -160,6 +172,19 @@ QUALITY_GATE_PROFILES = { "skip_local_accuracy_report": True, "skip_vedastro_live": False, }, + "runtime-truth": { + "skip_slow": True, + "skip_yoga_logic": True, + "skip_frontend_runtime": True, + "skip_frontend_click": True, + "frontend_click_mode": "core", + "check_release_hygiene": False, + "skip_real_cases": True, + "skip_dasha_audit": True, + "skip_oracle_audit": True, + "skip_local_accuracy_report": True, + "skip_vedastro_live": True, + }, } DASHA_REFERENCE_AUDIT_CMD = [ @@ -287,18 +312,14 @@ def format_failure_summary( def run(cmd: list[str], *, optional: bool = False, step: str | None = None, cwd: Path = ROOT) -> bool: label = step or " ".join(cmd[:2]) print(f"\n$ {' '.join(cmd)}") - completed = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True) - if completed.stdout: - print(completed.stdout, end="" if completed.stdout.endswith("\n") else "\n") - if completed.stderr: - print(completed.stderr, end="" if completed.stderr.endswith("\n") else "\n", file=sys.stderr) + completed = subprocess.run(cmd, cwd=cwd, text=True) if completed.returncode == 0: return True if optional: print(f"Optional step failed with exit code {completed.returncode}; continuing.") return False print( - format_failure_summary(label, cmd, completed.returncode, stdout=completed.stdout, stderr=completed.stderr, cwd=cwd), + format_failure_summary(label, cmd, completed.returncode, stdout="", stderr="", cwd=cwd), file=sys.stderr, ) raise SystemExit(completed.returncode) @@ -459,7 +480,7 @@ def run_profile(args: argparse.Namespace) -> dict: def main() -> int: parser = argparse.ArgumentParser(description="Run Jyotish skill quality gate") - parser.add_argument("--profile", choices=["quick", "browser", "release", "accuracy", "vedastro-live"], default="browser", help="Quality gate profile: quick, browser, release, accuracy, or vedastro-live") + parser.add_argument("--profile", choices=["quick", "browser", "release", "accuracy", "vedastro-live", "runtime-truth"], default="browser", help="Quality gate profile: quick, browser, release, accuracy, vedastro-live, or runtime-truth") parser.add_argument("--skip-slow", action="store_true", help="Skip slow golden-case regressions") parser.add_argument("--skip-yoga-logic", action="store_true", help="Skip Yoga logic comparison report refresh") parser.add_argument("--skip-frontend-runtime", action="store_true", help="Skip frontend build and runtime smoke") @@ -478,17 +499,37 @@ def main() -> int: os.environ.setdefault("PYTHONPATH", str(ROOT / "scripts")) print(f"\n== Quality gate profile: {args.profile} ==") print(json.dumps(profile, ensure_ascii=False, indent=2)) - compile_targets() - validate_json_files() - run([PYTHON, "scripts/audit_capabilities.py", "--mode", "validate"]) - run([PYTHON, "scripts/audit_fragments.py", "--strict"]) - run([PYTHON, "scripts/interpretation_source_inventory_gate.py"]) - run([PYTHON, "scripts/character_level_inventory_manifest.py", "--scope", "project", "--no-write", "--summary-only"]) - run([PYTHON, "scripts/deployment_preflight.py"]) - if profile["check_release_hygiene"]: - release_hygiene_check() - run([PYTHON, "scripts/validate_bphs_invariants.py"]) - pytest_targets = ["tests"] if args.all_tests else CORE_PYTEST_TARGETS + if args.profile == "runtime-truth": + for target in [ + ROOT / "scripts" / "jyotish_api_server.py", + ROOT / "scripts" / "diagnose_vedastro_mode.py", + ROOT / "scripts" / "diagnose_external_engine_adapters.py", + ROOT / "scripts" / "interpretation_source_runtime_coverage.py", + ROOT / "scripts" / "sync_final_evidence_packet_status.py", + ]: + py_compile.compile(str(target), doraise=True) + print(f"compiled {target.relative_to(ROOT)}") + run([PYTHON, "scripts/sync_final_evidence_packet_status.py"]) + run([PYTHON, "scripts/interpretation_source_inventory_gate.py"]) + run([PYTHON, "scripts/diagnose_vedastro_mode.py", "--json"]) + run([PYTHON, "scripts/diagnose_external_engine_adapters.py", "--json"]) + else: + compile_targets() + validate_json_files() + run([PYTHON, "scripts/audit_capabilities.py", "--mode", "validate"]) + run([PYTHON, "scripts/audit_fragments.py", "--strict"]) + run([PYTHON, "scripts/interpretation_source_inventory_gate.py"]) + run([PYTHON, "scripts/character_level_inventory_manifest.py", "--scope", "project", "--no-write", "--summary-only"]) + run([PYTHON, "scripts/deployment_preflight.py"]) + if profile["check_release_hygiene"]: + release_hygiene_check() + run([PYTHON, "scripts/validate_bphs_invariants.py"]) + if args.all_tests: + pytest_targets = ["tests"] + elif args.profile == "runtime-truth": + pytest_targets = RUNTIME_TRUTH_PYTEST_TARGETS + else: + pytest_targets = CORE_PYTEST_TARGETS run([PYTHON, "-m", "pytest", *pytest_targets]) if not profile["skip_frontend_runtime"]: run(["npm", "run", "build"], optional=False, cwd=APP) diff --git a/scripts/sync_final_evidence_packet_status.py b/scripts/sync_final_evidence_packet_status.py new file mode 100644 index 00000000..69f36aa9 --- /dev/null +++ b/scripts/sync_final_evidence_packet_status.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Sync latest final JHora evidence packet metadata with its numeric version.""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +WORK_DIR = ROOT / "scratch" / "local" / "pdf_review_123456" +PACKET_RE = re.compile(r"\.v(\d+)\.json$") + + +def latest_packet() -> tuple[int, Path]: + packets: list[tuple[int, Path]] = [] + for path in WORK_DIR.glob("jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v*.json"): + match = PACKET_RE.search(path.name) + if match: + packets.append((int(match.group(1)), path)) + if not packets: + raise SystemExit("no versioned JHora master evidence packets found") + return max(packets) + + +def main() -> int: + version, path = latest_packet() + packet = json.loads(path.read_text(encoding="utf-8")) + metadata = packet.setdefault("metadata", {}) + wanted_version = f"v{version}" + changed = False + for key, value in { + "status": "final_output_v1", + "current_version": wanted_version, + "packet_version": wanted_version, + "canonical_packet": path.name, + }.items(): + if metadata.get(key) != value: + metadata[key] = value + changed = True + if packet.get("status") != "final_output_v1": + packet["status"] = "final_output_v1" + changed = True + if changed: + path.write_text(json.dumps(packet, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + ledger = WORK_DIR / "evidence_packet_status_ledger_REDACTED_DATE_REDACTED_TIME.md" + if ledger.exists(): + text = ledger.read_text(encoding="utf-8") + line_re = re.compile( + r"\| Master evidence packet \| `jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME\.v\d+\.json` \| active \| Current canonical structured packet\. \|" + ) + wanted_line = f"| Master evidence packet | `{path.name}` | active | Current canonical structured packet. |" + new_text = line_re.sub(wanted_line, text, count=1) + if new_text != text: + ledger.write_text(new_text, encoding="utf-8") + + print(f"synced {path.name}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/unified_consultation_orchestrator.py b/scripts/unified_consultation_orchestrator.py index 6a3a48eb..d743bdd6 100644 --- a/scripts/unified_consultation_orchestrator.py +++ b/scripts/unified_consultation_orchestrator.py @@ -5,8 +5,24 @@ from __future__ import annotations from dataclasses import dataclass +from pathlib import Path from typing import Any +try: + from diagnose_pyjhora_adapter import build_report as build_pyjhora_adapter_report +except Exception: # pragma: no cover - import path varies in tests/CLI + from scripts.diagnose_pyjhora_adapter import build_report as build_pyjhora_adapter_report + +try: + from diagnose_jyotishganit_adapter import build_report as build_jyotishganit_adapter_report +except Exception: # pragma: no cover - import path varies in tests/CLI + from scripts.diagnose_jyotishganit_adapter import build_report as build_jyotishganit_adapter_report + +try: + from functional_benefics import derive_functional_benefic_malefic +except Exception: # pragma: no cover - import path varies in tests/CLI + from scripts.functional_benefics import derive_functional_benefic_malefic + @dataclass(frozen=True) class RouteDefinition: @@ -32,6 +48,25 @@ class UnifiedConsultationOrchestrator: "cross-check, and fallback when official calls are blocked." ), } + EVIDENCE_PACKET_REQUIRED_SECTIONS = [ + "D1", + "D9", + "D10", + "D2", + "D4", + "planet_degrees", + "house_degrees", + "dasha_boundaries", + "shadbala", + "ashtakavarga", + "yogas", + "UL", + "A7", + "A10", + "KP_cusp", + "external_oracle_status", + "vedastro_official_raw_response", + ] _THEME_ALIASES = { "relationship": "marriage", "marriage": "marriage", @@ -88,7 +123,7 @@ class UnifiedConsultationOrchestrator: "career": ["compute_chart", "run_rectification_gate", "run_thematic_report"], "relationship": ["compute_chart", "run_rectification_gate", "run_thematic_report"], "finance": ["compute_chart", "run_rectification_gate", "run_thematic_report"], - "timing": ["compute_chart", "run_rectification_gate", "run_thematic_report"], + "timing": ["compute_chart", "run_rectification_gate", "run_muhurta_panchanga", "run_thematic_report"], "general": ["compute_chart", "run_rectification_gate", "run_thematic_report"], } _ASYNC_CANDIDATES = [ @@ -119,6 +154,7 @@ class UnifiedConsultationOrchestrator: def resolve_route(self, question: str, themes: list[str] | None = None) -> dict[str, Any]: text = (question or "").lower() normalized_themes = themes or list(self._DEFAULT_THEMES) + explicit_timing_tokens = ("when", "timing", "何时", "什么时候", "应期", "几月", "哪月", "哪天", "日期") domain_tokens = { "career": ("career", "job", "work", "promotion", "business", "profession", "事业", "工作", "升职", "生意"), @@ -132,7 +168,9 @@ class UnifiedConsultationOrchestrator: if indexes: first_hits.append((min(indexes), route_name)) - if first_hits: + if text.strip() and any(token in text for token in explicit_timing_tokens) and "marriage" not in normalized_themes: + route = self._ROUTE_DEFINITIONS["timing"] + elif first_hits: route_name = sorted(first_hits, key=lambda item: item[0])[0][1] route = self._ROUTE_DEFINITIONS[route_name] elif not text.strip(): @@ -201,6 +239,9 @@ class UnifiedConsultationOrchestrator: if entry_mode == "rectification": sync_steps = [step for step in sync_steps if step != "run_rectification_gate"] sync_steps.insert(0, "run_rectification_gate") + elif entry_mode == "prashna": + sync_steps = [step for step in sync_steps if step not in {"compute_chart", "run_rectification_gate"}] + sync_steps.insert(0, "run_prashna") if high_rigor and "run_historical_event_backtest" not in sync_steps and events: sync_steps.append("run_historical_event_backtest") @@ -238,3 +279,400 @@ class UnifiedConsultationOrchestrator: "availability, cache policy, and async limits." ), } + + @staticmethod + def _vedastro_cloud_state(vedastro_official: dict[str, Any] | None) -> str: + official = vedastro_official if isinstance(vedastro_official, dict) else {} + runtime_truth = official.get("runtime_truth") if isinstance(official.get("runtime_truth"), dict) else {} + layers = runtime_truth.get("official_execution_layers") if isinstance(runtime_truth.get("official_execution_layers"), dict) else {} + status = str(runtime_truth.get("status") or official.get("status") or "blocked") + fallback_active = bool(runtime_truth.get("fallback_active") or official.get("fallback_used")) + if fallback_active: + return "local_fallback" + if layers.get("chart_core") == "ok" and status in {"ok", "partial", "available"}: + return "official_verified" + return "official_blocked" + + @staticmethod + def _section(value: Any, source_path: str) -> dict[str, Any]: + present = bool(value) + return { + "status": "used" if present else "missing", + "source_path": source_path, + } + + @staticmethod + def _external_engine_cross_validation(vedastro_state: str) -> dict[str, Any]: + repo_root = Path(__file__).resolve().parents[1] + pyjhora_refs = [ + repo_root / "docs/benchmark/jyotish_external_oracle_closure_master_dashboard.json", + repo_root / "references/oracle/artifacts/pyjhora_oracle_artifact_manifest.json", + ] + pyjhora_adapter = repo_root / "benchmarks/jyotish/scripts/run_pyjhora_compare.py" + pyjhora_adapter_report = build_pyjhora_adapter_report() + pyjhora_adapter_status = { + "available": "available", + "missing_dependency": f"blocked_missing_python_module:{pyjhora_adapter_report.get('missing_dependency') or 'jhora'}", + "missing_adapter": "blocked_missing_adapter_script", + }.get(str(pyjhora_adapter_report.get("status")), "runtime_error") + jyotishganit_ref = repo_root / "references/open_source_sources/jyotishganit" + jyotishganit_adapter_report = build_jyotishganit_adapter_report() + + engines = { + "VedAstro": { + "status": vedastro_state, + "runtime_invoked": vedastro_state == "official_verified", + "source_path": "vedastro_official.runtime_truth", + }, + "PyJHora/JHora": { + "status": ( + "reference_available_not_runtime_invoked" + if any(path.exists() for path in pyjhora_refs) + else "blocked_no_reference_artifact" + ), + "runtime_invoked": False, + "adapter_command": ( + "python3 benchmarks/jyotish/scripts/run_pyjhora_compare.py" + if pyjhora_adapter.exists() + else None + ), + "adapter_status": pyjhora_adapter_status, + "source_path": "docs/benchmark + references/oracle/artifacts", + }, + "jyotishganit": { + "status": ( + "reference_available_not_runtime_invoked" + if jyotishganit_ref.exists() + else "blocked_no_reference_checkout" + ), + "runtime_invoked": False, + "adapter_path": "references/open_source_sources/jyotishganit" if jyotishganit_ref.exists() else None, + "adapter_status": jyotishganit_adapter_report.get("status"), + "license": jyotishganit_adapter_report.get("license"), + "source_path": "references/open_source_sources/jyotishganit", + }, + } + status = "complete" if all(item["runtime_invoked"] for item in engines.values()) else "partial" + return { + "status": status, + "engines": engines, + "boundary": ( + "This records runtime/reference closure state only. Reference artifacts do not mean the engine was " + "invoked for the current consultation." + ), + } + + def machine_evidence_packet( + self, + *, + chart: dict[str, Any] | None, + route_packet: dict[str, Any], + vedastro_official: dict[str, Any] | None = None, + ) -> dict[str, Any]: + chart_data = chart if isinstance(chart, dict) else {} + modules = chart_data.get("modules") if isinstance(chart_data.get("modules"), dict) else {} + nested_chart = chart_data.get("chart") if isinstance(chart_data.get("chart"), dict) else {} + base_chart = modules.get("chart") if isinstance(modules.get("chart"), dict) else nested_chart or chart_data + varga = modules.get("varga_full") if isinstance(modules.get("varga_full"), dict) else {} + special_lagnas = ( + chart_data.get("special_lagnas") + if isinstance(chart_data.get("special_lagnas"), dict) + else modules.get("special_lagnas") if isinstance(modules.get("special_lagnas"), dict) else {} + ) + ascendant = base_chart.get("ascendant") if isinstance(base_chart.get("ascendant"), dict) else {} + ascendant_sign = ascendant.get("sign") if isinstance(ascendant, dict) else None + functional_layer = derive_functional_benefic_malefic(ascendant_sign) + official = vedastro_official if isinstance(vedastro_official, dict) else {} + raw_response = ( + official.get("raw_response") + or official.get("official_raw_response") + or official.get("raw_payload") + or official.get("raw") + ) + sections = { + "D1": self._section( + base_chart.get("planets") and base_chart.get("ascendant"), + "chart.planets+chart.ascendant", + ), + "D9": self._section(varga.get("D9_Navamsa") or varga.get("D9"), "modules.varga_full.D9"), + "D10": self._section(varga.get("D10_Dasamsa") or varga.get("D10"), "modules.varga_full.D10"), + "D2": self._section(varga.get("D2_Hora") or varga.get("D2"), "modules.varga_full.D2"), + "D4": self._section(varga.get("D4_Chaturthamsa") or varga.get("D4"), "modules.varga_full.D4"), + "planet_degrees": self._section(base_chart.get("planets"), "chart.planets"), + "house_degrees": self._section(base_chart.get("houses") or chart_data.get("houses"), "chart.houses"), + "dasha_boundaries": self._section(modules.get("dasha") or chart_data.get("dasha"), "modules.dasha"), + "shadbala": self._section(modules.get("shadbala") or chart_data.get("shadbala"), "modules.shadbala"), + "ashtakavarga": self._section(modules.get("ashtakavarga") or chart_data.get("ashtakavarga"), "modules.ashtakavarga"), + "yogas": self._section(modules.get("yogas") or chart_data.get("yogas"), "modules.yogas"), + "UL": self._section(special_lagnas.get("UL") or special_lagnas.get("Upapada_Lagna"), "special_lagnas.UL"), + "A7": self._section(special_lagnas.get("A7") or special_lagnas.get("Darapada"), "special_lagnas.A7"), + "A10": self._section(special_lagnas.get("A10") or special_lagnas.get("A10_Karma_Pada"), "special_lagnas.A10"), + "KP_cusp": self._section(modules.get("kp") or modules.get("kp_cusps") or chart_data.get("kp_cusps"), "modules.kp_cusps"), + "functional_benefic_malefic": self._section( + functional_layer if functional_layer.get("status") == "used" else None, + "chart.ascendant.sign -> scripts.functional_benefics", + ), + "external_oracle_status": { + "status": self._vedastro_cloud_state(vedastro_official), + "source_path": "vedastro_official.runtime_truth", + }, + "vedastro_official_raw_response": self._section(raw_response, "vedastro_official.raw_response"), + } + missing = [name for name, section in sections.items() if section.get("status") == "missing"] + return { + "status": "complete" if not missing else "partial", + "route": dict(route_packet), + "required_sections": list(self.EVIDENCE_PACKET_REQUIRED_SECTIONS), + "sections": sections, + "functional_benefic_malefic": functional_layer, + "missing_sections": missing, + } + + def real_case_calibration_catalog( + self, + *, + route_packet: dict[str, Any], + machine_evidence_packet: dict[str, Any] | None = None, + ) -> dict[str, Any]: + route = route_packet.get("question_type") or route_packet.get("primary_theme") or "general" + if route == "marriage": + route = "relationship" + case_index_by_domain = { + "career": ["references/real_case_studies/vedicka/career-success-poverty-prosperity.md"], + "finance": ["references/real_case_studies/vedicka/career-success-poverty-prosperity.md"], + "relationship": ["docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json"], + } + case_profiles = { + "references/real_case_studies/vedicka/career-success-poverty-prosperity.md": { + "domains": ["career", "finance"], + "evidence_sections": ["D1", "D10", "dasha_boundaries", "yogas"], + "recorded_outcome": "poverty_to_prosperity_global_recognition", + "event_trigger_keywords": ["Saturn dasha poverty", "Mercury dasha breakthrough", "Ketu dasha consolidation"], + }, + "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json": { + "domains": ["relationship"], + "evidence_sections": ["D1", "D9", "UL", "dasha_boundaries"], + "recorded_outcome": "relationship_structure_validation_dataset", + "event_trigger_keywords": ["UL", "Darapada", "7th lord", "DK"], + }, + } + candidate_refs = case_index_by_domain.get(route, []) + packet = machine_evidence_packet if isinstance(machine_evidence_packet, dict) else {} + sections = packet.get("sections") if isinstance(packet.get("sections"), dict) else {} + used_sections = {name for name, section in sections.items() if isinstance(section, dict) and section.get("status") == "used"} + dasha_used = "dasha_boundaries" in used_sections + external_oracle_status = ( + sections.get("external_oracle_status", {}).get("status") + if isinstance(sections.get("external_oracle_status"), dict) + else "missing" + ) + scored_candidates = [] + for ref in candidate_refs: + profile = case_profiles.get(ref, {"domains": [], "evidence_sections": []}) + overlap = sorted(used_sections & set(profile["evidence_sections"])) + trigger_score = (10 if dasha_used else 0) + (10 if external_oracle_status == "official_verified" else 0) + score = (50 if route in profile["domains"] else 0) + min(30, len(overlap) * 5) + trigger_score + scored_candidates.append({ + "case_source": ref, + "score": score, + "reference_grade": "partial_reference" if score >= 50 else "reference_only", + "recorded_outcome": profile.get("recorded_outcome"), + "similarities": { + "route_match": route in profile["domains"], + "evidence_section_overlap": overlap, + }, + "differences": { + "unmatched_required_sections": sorted(set(profile["evidence_sections"]) - used_sections), + }, + "event_trigger_match": { + "status": ( + "partial_match_official_timing_available" + if dasha_used and external_oracle_status == "official_verified" + else "partial_match_official_timing_blocked" + if dasha_used + else "not_matched_missing_dasha" + ), + "checks": { + "dasha_boundaries": "used" if dasha_used else "missing", + "external_oracle_status": external_oracle_status, + "recorded_trigger_keywords": list(profile.get("event_trigger_keywords", [])), + }, + "boundary": "Trigger check uses available timing evidence only; it is not event outcome validation.", + }, + "outcome_validation": { + "status": "local_outcome_recorded_trigger_not_replayed", + "recorded_outcome": profile.get("recorded_outcome"), + "boundary": "Outcome is read from the local case source profile; this does not replay the case chart or prove similarity.", + }, + }) + return { + "status": "partial_scored" if scored_candidates else "catalog_available_matching_not_run", + "batch_id": "real_case_studies_batch1", + "route": route, + "source_roots": ["references/real_case_studies", "docs/benchmark"], + "case_index_by_domain": case_index_by_domain, + "candidate_refs": list(candidate_refs), + "scored_candidates": scored_candidates, + "reference_grade": scored_candidates[0]["reference_grade"] if scored_candidates else "ungraded_until_similarity_scored", + "boundary": ( + "Local case catalog has route, evidence-section, and timing-evidence scoring only; concrete event " + "outcome matching must run before a case can be used as complete calibration evidence." + ), + } + + def runtime_evidence_log( + self, + *, + surface: str, + entry_mode: str, + route_packet: dict[str, Any], + executed_steps: list[str], + skipped_steps: list[str], + vedastro_official: dict[str, Any] | None = None, + interpretation_source_runtime_coverage: dict[str, Any] | None = None, + machine_evidence_packet: dict[str, Any] | None = None, + real_case_calibration: dict[str, Any] | None = None, + blind: bool = False, + ) -> dict[str, Any]: + official = vedastro_official if isinstance(vedastro_official, dict) else {} + runtime_truth = official.get("runtime_truth") if isinstance(official.get("runtime_truth"), dict) else {} + vedastro_state = self._vedastro_cloud_state(official) + external_cross_validation = self._external_engine_cross_validation(vedastro_state) + blocked_items: list[str] = [] + if vedastro_state != "official_verified": + blocked_items.append("vedastro_official_raw_snapshot_not_verified") + if external_cross_validation["status"] != "complete": + blocked_items.append("external_engine_cross_validation_partial") + packet = machine_evidence_packet if isinstance(machine_evidence_packet, dict) else {} + packet_status = packet.get("status") or "required_not_satisfied" + if not packet: + blocked_items.append("machine_evidence_packet_not_yet_materialized") + elif packet_status != "complete": + blocked_items.append("machine_evidence_packet_partial") + case_packet = real_case_calibration if isinstance(real_case_calibration, dict) else {} + case_status = case_packet.get("status") or "required_not_satisfied" + functional_packet = packet.get("functional_benefic_malefic") if isinstance(packet.get("functional_benefic_malefic"), dict) else {} + functional_status = functional_packet.get("status") or "blocked" + if functional_status != "used": + blocked_items.append("functional_benefic_malefic_blocked") + if not case_packet: + blocked_items.append("real_case_calibration_not_yet_materialized") + elif case_status != "complete": + blocked_items.append("real_case_calibration_partial") + technique_audit_table = [ + { + "technique": "VedAstro Cloud State", + "status": vedastro_state, + "used": vedastro_state == "official_verified", + "effect_on_confidence": ( + "official_cloud_evidence_available" + if vedastro_state == "official_verified" + else "confidence_capped_without_verified_official_cloud" + ), + }, + { + "technique": "External Engine Cross-Validation", + "status": external_cross_validation["status"], + "used": external_cross_validation["status"] == "complete", + "effect_on_confidence": ( + "three_engine_runtime_closure_available" + if external_cross_validation["status"] == "complete" + else "claims_capped_until_pyjhora_jhora_jyotishganit_are_invoked_for_this_run" + ), + }, + { + "technique": "Evidence Packet", + "status": packet_status, + "used": bool(packet), + "effect_on_confidence": "complete_packet_required_for_high_confidence" if packet_status != "complete" else "supports_high_confidence", + }, + { + "technique": "Blind Technical Mode", + "status": "used" if blind else "available_not_requested", + "used": bool(blind), + "effect_on_confidence": "prevents_conversation_feedback_leakage" if blind else "normal_runtime_mode", + }, + { + "technique": "MEVG / Global Web Evidence", + "status": "blocked", + "used": False, + "effect_on_confidence": "caps_claims_until_global_web_evidence_runs", + }, + { + "technique": "Real Case Calibration", + "status": case_status, + "used": bool(case_packet), + "effect_on_confidence": "partial_reference_only_until_outcome_replay" if case_status != "complete" else "supports_calibration", + }, + { + "technique": "Functional Benefic/Malefic", + "status": functional_status, + "used": functional_status == "used", + "key_functional_benefics": functional_packet.get("functional_benefics", []), + "key_functional_malefics": functional_packet.get("functional_malefics", []), + "yogakarakas": functional_packet.get("yogakarakas", []), + "effect_on_confidence": functional_packet.get( + "effect_on_confidence", + "high_rigor_claims_blocked_until_functional_nature_layer_is_present", + ), + }, + ] + return { + "name": "UnifiedConsultationRuntimeEvidenceLog", + "surface": surface, + "entry_mode": entry_mode, + "route": dict(route_packet), + "executed_steps": list(executed_steps), + "skipped_steps": list(skipped_steps), + "vedastro_cloud_state": vedastro_state, + "vedastro_runtime_truth": dict(runtime_truth), + "external_engine_cross_validation": external_cross_validation, + "source_priority": { + "mode": self.SOURCE_PRIORITY["mode"], + "priority": list(self.SOURCE_PRIORITY["priority"]), + }, + "evidence_sources": { + "vedastro_official": vedastro_state, + "local_modules": "used" if executed_steps else "not_used", + "interpretation_source_runtime_coverage": ( + "used" if isinstance(interpretation_source_runtime_coverage, dict) and interpretation_source_runtime_coverage else "not_used" + ), + }, + "evidence_packet_contract": { + "status": packet_status, + "required_sections": list(self.EVIDENCE_PACKET_REQUIRED_SECTIONS), + "missing_sections": packet.get("missing_sections", []), + }, + "blind_technical_mode": { + "enabled": bool(blind), + "allowed_sources": ["birth_payload", "pdf", "machine_evidence_packet"], + "disallowed_sources": ["conversation_feedback", "memory_linked_personal_history"], + }, + "real_case_calibration": { + "status": case_status, + "required_fields": [ + "case_source", + "chart_similarity", + "transit_or_dasha_trigger", + "event", + "similarities", + "differences", + "reference_grade", + ], + }, + "quality_gate": { + "technique_audit_table_required": True, + "technique_audit_table": technique_audit_table, + "required_rows": [ + "VedAstro Cloud State", + "External Engine Cross-Validation", + "Evidence Packet", + "Blind Technical Mode", + "MEVG / Global Web Evidence", + "Real Case Calibration", + "Functional Benefic/Malefic", + ], + "status": "blocked" if blocked_items else "pass", + "blocked_items": blocked_items, + }, + } diff --git a/scripts/vedastro_official_mcp_bridge.py b/scripts/vedastro_official_mcp_bridge.py index d7268b1e..3a30413e 100644 --- a/scripts/vedastro_official_mcp_bridge.py +++ b/scripts/vedastro_official_mcp_bridge.py @@ -98,12 +98,44 @@ def _tools_list(endpoint: str) -> dict[str, Any]: } +def _call_tool(endpoint: str, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]: + init = _initialize(endpoint) + payload = { + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": {"name": tool_name, "arguments": arguments}, + } + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + headers = { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + } + if init.get("session_id"): + headers["Mcp-Session-Id"] = str(init["session_id"]) + req = urllib.request.Request(endpoint, data=body, method="POST", headers=headers) + with urllib.request.urlopen(req, timeout=DEFAULT_TIMEOUT_SECONDS) as resp: + raw = resp.read().decode("utf-8") + response_headers = {key.lower(): value for key, value in resp.headers.items()} + result = json.loads(raw) + return { + "endpoint": endpoint, + "available": True, + "status": "ok", + "operation": "call_tool", + "tool_name": tool_name, + "session_id": init.get("session_id") or response_headers.get("mcp-session-id"), + "result": result.get("result") or result, + "source": "official_public_mcp", + } + + def schema() -> dict[str, Any]: return { "bridge": "vedastro_official_mcp_bridge", "role": "official_public_mcp_thin_bridge", "endpoint": DEFAULT_ENDPOINT, - "operations": ["initialize", "tools_list"], + "operations": ["initialize", "tools_list", "call_tool"], "response_contract": [ "endpoint", "available", @@ -113,7 +145,7 @@ def schema() -> dict[str, Any]: "source", ], "boundaries": [ - "Use for official MCP reachability and tool discovery.", + "Use for official MCP reachability, tool discovery, and targeted tool calls.", "Do not let raw MCP results directly override local score/dominant_label/payout_label.", "Promote only through explicit local contracts and tests.", ], @@ -138,7 +170,9 @@ def main() -> int: parser = argparse.ArgumentParser(description="Official public VedAstro MCP bridge") parser.add_argument("--print-schema", action="store_true") parser.add_argument("--endpoint", default=DEFAULT_ENDPOINT) - parser.add_argument("--operation", choices=["initialize", "tools_list"], default="tools_list") + parser.add_argument("--operation", choices=["initialize", "tools_list", "call_tool"], default="tools_list") + parser.add_argument("--tool") + parser.add_argument("--arguments-json", default="{}") args = parser.parse_args() if args.print_schema: @@ -147,6 +181,13 @@ def main() -> int: try: if args.operation == "initialize": result = _initialize(args.endpoint) + elif args.operation == "call_tool": + if not args.tool: + raise ValueError("--tool is required when --operation call_tool") + arguments = json.loads(args.arguments_json) + if not isinstance(arguments, dict): + raise ValueError("--arguments-json must decode to an object") + result = _call_tool(args.endpoint, args.tool, arguments) else: result = _tools_list(args.endpoint) except Exception as exc: # noqa: BLE001 diff --git a/scripts/vedastro_service_adapter.py b/scripts/vedastro_service_adapter.py index 71752352..c2568fb1 100644 --- a/scripts/vedastro_service_adapter.py +++ b/scripts/vedastro_service_adapter.py @@ -2389,6 +2389,13 @@ def _normalize_official_full_snapshot_success( "operation": "official_full_snapshot", "primary_source": "vedastro_official", "snapshot_sections": sections, + "raw_response": { + "source": "vedastro_official_full_snapshot", + "sections": sections, + "section_statuses": section_statuses, + "request_manifest": manifest, + "response_hash": metadata["response_hash"], + }, "official_chart": _build_official_chart_from_snapshot(sections), "official_full_capability_catalog": official_full_capability_catalog or {}, "section_statuses": section_statuses, diff --git a/scripts/vedastro_user_entrypoint.py b/scripts/vedastro_user_entrypoint.py index 06cc1342..fb616659 100644 --- a/scripts/vedastro_user_entrypoint.py +++ b/scripts/vedastro_user_entrypoint.py @@ -151,14 +151,23 @@ def _strict_workflow_summary(route: str, catalog: dict[str, Any]) -> dict[str, A def _cache_and_queue_report() -> dict[str, Any]: + queue_enabled = ( + _bool_env("VEDASTRO_FREE_TIER_QUEUE") + or _bool_env("VEDASTRO_FREE_TIER_QUEUE_ENABLED") + or _bool_env("VEDASTRO_ENABLE_FREE_TIER_QUEUE") + ) + using_free_tier = not bool(os.environ.get("VEDASTRO_API_KEY", "").strip()) return { "official_full_snapshot_cache_scope": "official_full_snapshot_semantic_cache", "official_full_snapshot_cache_ttl_seconds": _int_env("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", 0), "range_scan_cache_scope": "vedastro_range_scan_request_cache", "range_scan_cache_ttl_seconds": _int_env("VEDASTRO_CACHE_TTL_SECONDS", 0), - "free_tier_queue_enabled": _bool_env("VEDASTRO_FREE_TIER_QUEUE") - or _bool_env("VEDASTRO_FREE_TIER_QUEUE_ENABLED") - or _bool_env("VEDASTRO_ENABLE_FREE_TIER_QUEUE"), + "free_tier_queue_enabled": queue_enabled, + "free_tier_strategy": { + "using_free_tier": using_free_tier, + "queue_enabled": queue_enabled, + "guard_status": "within_free_tier_strategy" if using_free_tier else "premium_key_present", + }, "sample_limit": _int_env("VEDASTRO_FULL_CATALOG_SAMPLE_LIMIT", 0), "artifact_root": "scratch/local/vedastro_adapter", } diff --git a/tests/test_api_server_security.py b/tests/test_api_server_security.py index a98d8bf1..cc513a97 100644 --- a/tests/test_api_server_security.py +++ b/tests/test_api_server_security.py @@ -387,7 +387,10 @@ def test_high_rigor_workflow_plan_only_exposes_official_hard_override_contract() assert result['execution_plan'][-1] == 'return_official_primary_supplemental_fallback_conflict_contract' -def test_high_rigor_vedastro_official_summary_passes_through_contract_fields() -> None: +def test_high_rigor_vedastro_official_summary_passes_through_contract_fields(monkeypatch) -> None: + monkeypatch.delenv('VEDASTRO_FREE_TIER_QUEUE', raising=False) + monkeypatch.delenv('VEDASTRO_FREE_TIER_QUEUE_ENABLED', raising=False) + monkeypatch.delenv('VEDASTRO_ENABLE_FREE_TIER_QUEUE', raising=False) handler = _handler() chart = { @@ -428,6 +431,17 @@ def test_high_rigor_vedastro_official_summary_passes_through_contract_fields() - assert result['fallback_used'] == ['local_chart_fallback'] assert result['blocked_items'] == ['official_event_radar_partial'] assert result['conflicts'] == [{'type': 'official_local_dasha_conflict'}] + runtime_truth = result['runtime_truth'] + assert runtime_truth['catalog_boundary'] == 'catalog_recognized_not_full_runtime_execution' + assert runtime_truth['official_execution_layers']['chart_core'] == 'ok' + assert runtime_truth['official_execution_layers']['event_radar'] == 'partial' + assert runtime_truth['official_execution_layers']['catalog_status'] == 'partial' + assert runtime_truth['fallback_active'] is True + assert runtime_truth['blocked_items'] == ['official_event_radar_partial'] + assert runtime_truth['free_tier_strategy']['using_free_tier'] is True + assert runtime_truth['free_tier_strategy']['queue_enabled'] is False + assert runtime_truth['free_tier_strategy']['cache_hit'] is False + assert runtime_truth['free_tier_strategy']['guard_status'] == 'degraded_or_partial' def test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_full_snapshot() -> None: @@ -481,6 +495,7 @@ def test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_f 'summary': {'catalog_method_count': 641}, } }, + 'raw_response': {'official': 'raw'}, }, }, 'ai_prompt_pack': { @@ -534,6 +549,49 @@ def test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_f assert result['verdict'] == 'high_probability_window' assert result['dominant_label'] == 'career_status' assert result['main_conflicts'] == [{'type': 'official_local_dasha_conflict'}] + assert result['runtime_truth']['primary_route'] == 'career' + assert result['runtime_truth']['routes_available'] == ['career', 'relationship', 'finance'] + assert result['raw_response'] == {'official': 'raw'} + + +def test_high_rigor_vedastro_official_summary_uses_module_snapshot_cache_truth_when_range_scan_snapshot_missing() -> None: + handler = _handler() + chart = { + 'birth': {'ayanamsa_display': 'Lahiri', 'ayanamsa_name': 'lahiri', 'node_mode': 'mean'}, + 'modules': { + 'vedastro_range_scan_result': { + 'status': 'ok', + 'source_metadata': {}, + }, + 'vedastro_official_full_snapshot': { + 'status': 'official_snapshot_budget_exhausted', + 'strict_workflow_primary_route': 'career', + 'strict_workflow_routes_available': ['career'], + 'strict_workflow_contracts': { + 'career': { + 'official_primary_evidence': {'chart_core': {'status': 'ok'}}, + }, + }, + 'source_metadata': { + 'semantic_cache': { + 'cache_hit': True, + }, + }, + }, + }, + 'ai_prompt_pack': { + 'evidence_snapshot': { + 'vedastro_official_snapshot': { + 'status': 'official_snapshot_budget_exhausted', + }, + }, + }, + } + + result = handler._high_rigor_vedastro_official_summary(chart) + + assert result['status'] == 'official_snapshot_budget_exhausted' + assert result['runtime_truth']['free_tier_strategy']['cache_hit'] is True def test_api_prompt_pack_official_snapshot_carries_strict_workflow_contracts() -> None: @@ -708,6 +766,7 @@ def test_consultation_workflow_surfaces_top_reader_contract_in_official_summary( 'lon': 114.2, 'tz': 8, 'theme': ['career'], + 'blind': True, }) contract = result['vedastro_official']['strict_workflow_contracts']['career'] @@ -2127,6 +2186,7 @@ def test_consultation_workflow_uses_unified_orchestrator_contract(monkeypatch) - 'lon': 114.2, 'tz': 8, 'theme': ['career'], + 'blind': True, }) assert result['success'] is True @@ -2144,8 +2204,24 @@ def test_consultation_workflow_uses_unified_orchestrator_contract(monkeypatch) - 'run_rectification_gate', 'run_thematic_report', ] - assert result['runtime_planner']['skipped_steps'] == ['run_historical_event_backtest'] + assert 'run_historical_event_backtest' in result['runtime_planner']['skipped_steps'] assert result['source_priority']['mode'] == 'vedastro_official_snapshot_first' + assert result['runtime_evidence_log']['surface'] == 'api_web' + assert result['runtime_evidence_log']['route']['question_type'] == 'career' + assert result['runtime_evidence_log']['vedastro_cloud_state'] in { + 'official_verified', + 'official_blocked', + 'local_fallback', + } + assert result['runtime_evidence_log']['quality_gate']['technique_audit_table_required'] is True + assert result['runtime_evidence_log']['quality_gate']['technique_audit_table'][0]['technique'] == 'VedAstro Cloud State' + assert result['machine_evidence_packet']['status'] == 'partial' + assert result['real_case_calibration']['status'] == 'partial_scored' + assert result['real_case_calibration']['batch_id'] == 'real_case_studies_batch1' + assert result['runtime_evidence_log']['evidence_packet_contract']['status'] == 'partial' + assert result['runtime_evidence_log']['real_case_calibration']['status'] == 'partial_scored' + assert result['runtime_evidence_log']['blind_technical_mode']['enabled'] is True + assert 'conversation_feedback' in result['runtime_evidence_log']['blind_technical_mode']['disallowed_sources'] assert result['chart']['special_lagnas']['precision'] == 'sunrise_correct' @@ -2345,6 +2421,183 @@ def test_consultation_workflow_rectification_entry_sends_empty_objects_before_ch assert seen['rectification_body']['ascendant'] == {} +def test_consultation_workflow_prashna_entry_uses_prashna_without_compute_chart(monkeypatch) -> None: + handler = _handler() + calls = {'chart': 0, 'prashna': 0} + seen = {} + + def fake_chart_compute(body): + calls['chart'] += 1 + return {'success': True, 'modules': {}} + + def fake_prashna(body): + calls['prashna'] += 1 + seen['prashna_body'] = dict(body) + return { + 'success': True, + 'endpoint': 'prashna', + 'question': body.get('question'), + 'timing': {'recommendation': '可以进行Prashna分析'}, + 'judgement': {'summary': '可问'}, + } + + def fake_thematic_report(body): + seen['theme_body'] = dict(body) + return { + 'success': True, + 'endpoint': 'thematic_report', + 'mode': 'upstream_contract_reuse', + 'report': {'sections': []}, + } + + monkeypatch.setattr(handler, '_compute_chart', fake_chart_compute) + monkeypatch.setattr(handler, '_compute_prashna', fake_prashna) + monkeypatch.setattr(handler, '_compute_thematic_report', fake_thematic_report) + monkeypatch.setattr(handler, '_compute_rectification_gate', lambda body: {'success': True, 'summary': {'recommended_events': []}}) + + result = handler._compute_consultation_workflow({ + 'entry_mode': 'prashna', + 'question': '这个合作能成吗', + 'question_text': '这个合作能成吗', + 'theme': ['wealth'], + 'year': REDACTED_YEAR, + 'month': 4, + 'day': 17, + 'hour': 14, + 'minute': 49, + 'lat': 36.42, + 'lon': 114.2, + 'tz': 8, + }) + + assert result['success'] is True + assert result['entry_mode'] == 'prashna' + assert result['runtime_planner']['entry_mode'] == 'prashna' + assert result['runtime_planner']['executed_steps'] == ['run_prashna', 'run_thematic_report'] + assert calls['chart'] == 0 + assert calls['prashna'] == 1 + assert seen['prashna_body']['question'] == '这个合作能成吗' + + +def test_consultation_workflow_builds_audited_remedies_from_guided_topic_gate(monkeypatch) -> None: + handler = _handler() + + fake_chart = { + 'success': True, + 'birth_info': {'date': 'REDACTED_DATE', 'time': 'REDACTED_TIME', 'tz': 8}, + 'ascendant': {'lon': 92.0, 'sign': 'Cancer'}, + 'planets': _sample_planets(), + 'chart': { + 'ascendant': {'lon': 92.0, 'sign': 'Cancer'}, + 'planets': _sample_planets(), + }, + 'modules': { + 'guided_topics': [ + { + 'id': 'career', + 'title': '事业', + 'strict_audit_gate': { + 'topic': 'career', + 'primary_planets': ['Saturn'], + 'active_dasha_lord': 'Saturn', + 'strength_context': { + 'Saturn': {'total_rupas': 0.42, 'strength_level': 'weak'}, + }, + 'dosha_context': ['delay_signature'], + }, + }, + ], + }, + 'special_lagnas': {'precision': 'sunrise_correct'}, + } + + monkeypatch.setattr(handler, '_compute_chart', lambda body: fake_chart) + monkeypatch.setattr(handler, '_compute_rectification_gate', lambda body: {'success': True, 'summary': {'recommended_events': []}}) + monkeypatch.setattr(handler, '_compute_thematic_report', lambda body: {'success': True, 'endpoint': 'thematic_report', 'report': {'sections': []}}) + + result = handler._compute_consultation_workflow({ + 'entry_mode': 'direct_chart', + 'question': '请直接排盘并看事业', + 'theme': ['career'], + 'year': REDACTED_YEAR, + 'month': 4, + 'day': 17, + 'hour': 14, + 'minute': 49, + 'lat': 36.42, + 'lon': 114.2, + 'tz': 8, + }) + remedies = result.get('audited_remedies') or {} + assert remedies['status'] == 'ok' + assert remedies['source'] == 'strict_audit_gate' + assert remedies['topic'] == 'career' + assert remedies['active_dasha_lord'] == 'Saturn' + + +def test_consultation_workflow_timing_route_builds_muhurta_panchanga_sidecar(monkeypatch) -> None: + handler = _handler() + fake_chart = { + 'success': True, + 'birth_info': {'date': 'REDACTED_DATE', 'time': 'REDACTED_TIME', 'tz': 8}, + 'ascendant': {'lon': 92.0, 'sign': 'Cancer'}, + 'planets': _sample_planets(), + 'chart': { + 'ascendant': {'lon': 92.0, 'sign': 'Cancer'}, + 'planets': _sample_planets(), + }, + 'modules': {}, + 'special_lagnas': {'precision': 'sunrise_correct'}, + } + seen = {} + + monkeypatch.setattr(handler, '_compute_chart', lambda body: fake_chart) + monkeypatch.setattr(handler, '_compute_rectification_gate', lambda body: { + 'success': True, + 'endpoint': 'rectification_gate', + 'summary': {'recommended_events': []}, + }) + monkeypatch.setattr(handler, '_compute_thematic_report', lambda body: { + 'success': True, + 'endpoint': 'thematic_report', + 'mode': 'derived_chart_evidence', + 'theme_count': len(body.get('theme') or []), + }) + + def fake_muhurta(body): + seen['muhurta_body'] = dict(body) + return { + 'status': 'ok', + 'source': 'local_muhurta.py', + 'activity': 'business', + 'report_mode': 'muhurta_date_range_solver', + 'panchanga': {'query_date': '2026-07-08'}, + 'best_windows': [{'date': '2026-07-08'}], + } + + monkeypatch.setattr(handler, '_compute_muhurta_panchanga', fake_muhurta) + + result = handler._compute_consultation_workflow({ + 'entry_mode': 'direct_chart', + 'question': '2026年何时适合谈合作和推进项目的应期', + 'year': REDACTED_YEAR, + 'month': 4, + 'day': 17, + 'hour': 14, + 'minute': 49, + 'lat': 36.42, + 'lon': 114.2, + 'tz': 8, + 'reference_date': '2026-07-08', + 'theme': ['career'], + }) + assert result['success'] is True + assert 'run_muhurta_panchanga' in result['runtime_planner']['executed_steps'] + assert result['muhurta_panchanga']['status'] == 'ok' + assert result['muhurta_panchanga']['activity'] == 'business' + assert seen['muhurta_body']['reference_date'] == '2026-07-08' + + def test_thematic_report_handles_missing_dasa_convergence_without_crash(monkeypatch) -> None: handler = _handler() diff --git a/tests/test_bv_raman_report_book_acceptance.py b/tests/test_bv_raman_report_book_acceptance.py new file mode 100644 index 00000000..06019f5d --- /dev/null +++ b/tests/test_bv_raman_report_book_acceptance.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Acceptance tests for the long-report book root and its failure-discipline docs.""" + +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +REPORT_ROOT = ROOT / "docs" / "reports" / "chart_research_REDACTED_DATE_REDACTED_TIME" + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def test_report_book_root_has_required_navigation_and_seed_boundary() -> None: + readme = _read(REPORT_ROOT / "README.md") + book = _read(REPORT_ROOT / "book.md") + + assert "Source Priority" in readme + assert "Seed Draft Mapping" in readme + assert "current `Raman/Mean` run" in readme + assert "PDF export: blocked" in readme + + assert "## Front Matter" in book + assert "## Core Interpretation" in book + assert "## Varga Layers" in book + assert "## Appendices" in book + + +def test_high_value_chapters_expose_current_runtime_conflicts_and_not_old_draft_as_truth() -> None: + chapter_01 = _read(REPORT_ROOT / "chapters" / "01_methodology_and_honesty_boundary.md") + chapter_04 = _read(REPORT_ROOT / "chapters" / "04_d1_rasi_foundation.md") + chapter_08 = _read(REPORT_ROOT / "chapters" / "08_varga_framework_overview.md") + chapter_16 = _read(REPORT_ROOT / "chapters" / "16_parashara_vimshottari_yogini_kalachakra.md") + appendix_f = _read(REPORT_ROOT / "appendices" / "F_technique_audit_table.md") + + assert "current raw JSON wins" in chapter_01 + assert "Numeric values there may differ" in chapter_04 + assert "seed draft's divisional table contains some values from an older calculation mouthpiece" in chapter_08 + assert "does **not** match older draft sections" in chapter_16 + assert "current run says Saturn/Venus active" in appendix_f + + +def test_error_ledger_exists_and_tracks_real_migration_failures() -> None: + ledger = _read(REPORT_ROOT / "appendices" / "H_error_ledger_and_preflight.md") + readme = _read(REPORT_ROOT / "README.md") + + assert "Read this before the next editing pass" in ledger + assert "old draft vs current runtime conflict" in ledger + assert "D9" in ledger + assert "Saturn/Ketu" in ledger + assert "Saturn/Venus" in ledger + assert "before editing chapters" in readme + assert "H_error_ledger_and_preflight.md" in readme + + +def test_report_book_counts_and_cross_links_stay_complete() -> None: + chapters = sorted((REPORT_ROOT / "chapters").glob("*.md")) + appendices = sorted((REPORT_ROOT / "appendices").glob("*.md")) + book = _read(REPORT_ROOT / "book.md") + readme = _read(REPORT_ROOT / "README.md") + appendix_h = _read(REPORT_ROOT / "appendices" / "H_error_ledger_and_preflight.md") + + assert len(chapters) == 22 + assert len(appendices) == 8 + assert "current runtime wins" in appendix_h + assert "Appendix H: error ledger and preflight" in readme + assert "[H. Error Ledger and Preflight]" in book + assert "D9 Leo 10.7493" in appendix_h + + +def test_runtime_truth_markers_exist_in_key_chapters() -> None: + chapter_08 = _read(REPORT_ROOT / "chapters" / "08_varga_framework_overview.md") + chapter_15 = _read(REPORT_ROOT / "chapters" / "15_jaimini_chara_karaka_arudha_narayana.md") + chapter_20 = _read(REPORT_ROOT / "chapters" / "20_future_period_windows_2026_2063.md") + + assert "D9 | Leo 10.7493" in chapter_08 + assert "A10 / Karma Pada = `Capricorn`" in chapter_15 + assert "Saturn/Venus" in chapter_20 + + +def test_varga_topic_chapters_are_no_longer_skeleton_only() -> None: + chapter_09 = _read(REPORT_ROOT / "chapters" / "09_d2_d11_wealth_structure.md") + chapter_10 = _read(REPORT_ROOT / "chapters" / "10_d3_d4_d7_d12_family_property_lineage.md") + chapter_11 = _read(REPORT_ROOT / "chapters" / "11_d9_marriage_dharma_and_relationship_pattern.md") + chapter_12 = _read(REPORT_ROOT / "chapters" / "12_d10_career_status_and_public_work.md") + chapter_13 = _read(REPORT_ROOT / "chapters" / "13_d16_d20_d24_d27_d30_special_topics.md") + chapter_14 = _read(REPORT_ROOT / "chapters" / "14_d40_d45_d60_karmic_layers.md") + + assert "Skeleton only." not in chapter_09 + assert "finance_strict_evidence" in chapter_09 + assert "D2 Hora" in chapter_09 + + assert "Skeleton only." not in chapter_10 + assert "D4 Chaturthamsa" in chapter_10 + assert "D12 Dwadasamsa" in chapter_10 + + assert "Skeleton only." not in chapter_11 + assert "relationship_strict_evidence" in chapter_11 + assert "D9" in chapter_11 + + assert "Skeleton only." not in chapter_12 + assert "career_strict_evidence" in chapter_12 + assert "A10 Karma Pada" in chapter_12 + + assert "Skeleton only." not in chapter_13 + assert "Vimsopaka" in chapter_13 + assert "D30" in chapter_13 + + assert "Skeleton only." not in chapter_14 + assert "D60" in chapter_14 + assert "birth-time sensitive" in chapter_14 + + +def test_remaining_high_value_chapters_are_grounded_and_not_skeletons() -> None: + chapter_17 = _read(REPORT_ROOT / "chapters" / "17_transit_tajika_varshaphala_2026_2063.md") + chapter_19 = _read(REPORT_ROOT / "chapters" / "19_past_event_validation_and_conflict_notes.md") + chapter_21 = _read(REPORT_ROOT / "chapters" / "21_remedies_practice_and_limits.md") + + assert "Skeleton only." not in chapter_17 + assert "solar_return" in chapter_17 + assert "tajika" in chapter_17 + assert "dasha convergence" in chapter_17.lower() + + assert "Skeleton only." not in chapter_19 + assert "historical_event_backtest.py" in chapter_19 + assert "strong_hit / weak_hit / miss / blocked" in chapter_19 + assert "Appendix H" in chapter_19 + + assert "Skeleton only." not in chapter_21 + assert "BPHS补救系统 v1.0" in chapter_21 + assert "low-risk measures first" in chapter_21 + assert "gemstones" in chapter_21 + + +def test_front_matter_and_appendix_tables_are_no_longer_placeholders() -> None: + chapter_02 = _read(REPORT_ROOT / "chapters" / "02_birth_data_chart_core_and_engine_contract.md") + chapter_03 = _read(REPORT_ROOT / "chapters" / "03_executive_synthesis.md") + appendix_a = _read(REPORT_ROOT / "appendices" / "A_raw_chart_tables.md") + appendix_b = _read(REPORT_ROOT / "appendices" / "B_dasha_boundaries_full.md") + appendix_c = _read(REPORT_ROOT / "appendices" / "C_varga_positions_full.md") + appendix_d = _read(REPORT_ROOT / "appendices" / "D_shadbala_ashtakavarga_tables.md") + + assert "What belongs here" not in chapter_02 + assert "local_engine_fallback" in chapter_02 + assert "official_evidence" in chapter_02 + + assert "Skeleton only." not in chapter_03 + assert "high-structure, late-ripening chart" in chapter_03 + + assert "Ready to expand" not in appendix_a + assert "Core chart table" in appendix_a + assert "Sun | Aries" in appendix_a + + assert "Ready to expand" not in appendix_b + assert "Current Vimshottari" in appendix_b + assert "Current Narayana" in appendix_b + + assert "Ready to expand" not in appendix_c + assert "Key ascendants" in appendix_c + assert "D10 | Sagittarius 25.277" in appendix_c + + assert "Ready to expand" not in appendix_d + assert "Shadbala ranking" in appendix_d + assert "SAV summary" in appendix_d diff --git a/tests/test_chart_research_export_tools.py b/tests/test_chart_research_export_tools.py new file mode 100644 index 00000000..5aaa96d2 --- /dev/null +++ b/tests/test_chart_research_export_tools.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Regression tests for report export and CLI entrypoints.""" + +from __future__ import annotations + +import subprocess +import sys +from shutil import which +from pathlib import Path + +import pytest +from PIL import Image +from pypdf import PdfReader + + +ROOT = Path(__file__).resolve().parents[1] +REPORT_ROOT = ROOT / "docs" / "reports" / "chart_research_REDACTED_DATE_REDACTED_TIME" + + +def test_historical_event_backtest_cli_help_runs_from_repo_root() -> None: + completed = subprocess.run( + [ + sys.executable, + "scripts/historical_event_backtest.py", + "--help", + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=30, + check=False, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout + assert "historical-event backtest" in (completed.stdout + completed.stderr).lower() + + +def test_chart_research_pdf_export_generates_readable_pdf(tmp_path: Path) -> None: + output_pdf = tmp_path / "chart_research.pdf" + completed = subprocess.run( + [ + sys.executable, + "scripts/export_chart_research_pdf.py", + "--report-root", + str(REPORT_ROOT), + "--output", + str(output_pdf), + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=90, + check=False, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout + assert output_pdf.exists() + assert output_pdf.stat().st_size > 0 + + reader = PdfReader(str(output_pdf)) + assert len(reader.pages) >= 3 + + first_page = reader.pages[0].extract_text() or "" + all_text = "\n".join((page.extract_text() or "") for page in reader.pages[:5]) + assert "B.V. Raman" in first_page or "B.V. Raman" in all_text + assert "Executive Synthesis" in all_text or "Birth Data" in all_text + + +@pytest.mark.skipif(which("pdftoppm") is None, reason="pdftoppm not installed") +def test_chart_research_pdf_export_renders_visible_first_page(tmp_path: Path) -> None: + output_pdf = tmp_path / "chart_research.pdf" + export = subprocess.run( + [ + sys.executable, + "scripts/export_chart_research_pdf.py", + "--report-root", + str(REPORT_ROOT), + "--output", + str(output_pdf), + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=90, + check=False, + ) + assert export.returncode == 0, export.stderr or export.stdout + + png_prefix = tmp_path / "chart_research_page_1" + render = subprocess.run( + [ + "pdftoppm", + "-png", + "-f", + "1", + "-singlefile", + str(output_pdf), + str(png_prefix), + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=60, + check=False, + ) + assert render.returncode == 0, render.stderr or render.stdout + + first_page_png = png_prefix.with_suffix(".png") + assert first_page_png.exists() + + img = Image.open(first_page_png).convert("L") + dark_pixels = sum(1 for pixel in img.getdata() if pixel < 250) + assert dark_pixels > 1000 diff --git a/tests/test_codex_plugin_wrapper.py b/tests/test_codex_plugin_wrapper.py new file mode 100644 index 00000000..aca5fb13 --- /dev/null +++ b/tests/test_codex_plugin_wrapper.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +import json +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_local_codex_plugin_manifest_points_at_repo_truth() -> None: + plugin_json = ROOT / ".codex-plugin" / "plugin.json" + assert plugin_json.exists() + + payload = json.loads(plugin_json.read_text(encoding="utf-8")) + assert payload["name"] == "jyotish-vedic-astrology" + assert payload["skills"] == "./skills/" + + mcp_servers = payload["mcpServers"] + assert isinstance(mcp_servers, dict) + assert "jyotish" in mcp_servers + jyotish = mcp_servers["jyotish"] + assert jyotish["command"] == "python3" + assert jyotish["args"] == ["./mcp_server.py"] + + assert (ROOT / "skills").is_dir() + assert (ROOT / "mcp_server.py").is_file() diff --git a/tests/test_external_engine_adapter_diagnostics.py b/tests/test_external_engine_adapter_diagnostics.py new file mode 100644 index 00000000..e98df1fa --- /dev/null +++ b/tests/test_external_engine_adapter_diagnostics.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_external_engine_adapter_diagnostics_aggregates_three_engines() -> None: + completed = subprocess.run( + [sys.executable, "scripts/diagnose_external_engine_adapters.py", "--json"], + cwd=ROOT, + text=True, + capture_output=True, + timeout=30, + check=False, + env={**os.environ, "JYOTISH_SKIP_LOCAL_ENV": "1"}, + ) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + assert report["scope"] == "external_engine_adapter_diagnostics" + assert set(report["engines"]) == {"VedAstro", "PyJHora/JHora", "jyotishganit"} + assert report["engines"]["VedAstro"]["official_closure_plan"]["raw_response_acceptance"].startswith("vedastro_official.raw_response") + assert report["engines"]["PyJHora/JHora"]["status"] in {"available", "missing_dependency", "missing_adapter"} + assert report["engines"]["PyJHora/JHora"]["install_hint"]["package"] == "PyJHora" + assert report["engines"]["PyJHora/JHora"]["license_boundary"].startswith("AGPL external benchmark") + assert report["engines"]["jyotishganit"]["license"] == "MIT" + assert report["status"] in {"complete", "partial"} diff --git a/tests/test_final_jhora_evidence_packet_acceptance.py b/tests/test_final_jhora_evidence_packet_acceptance.py new file mode 100644 index 00000000..745ce366 --- /dev/null +++ b/tests/test_final_jhora_evidence_packet_acceptance.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Acceptance guard for the final JHora/PDF evidence packet artifacts.""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + +import scripts.sync_final_evidence_packet_status as sync_status + + +ROOT = Path(__file__).resolve().parents[1] +WORK_DIR = ROOT / "scratch" / "local" / "pdf_review_123456" +ERROR_LOG = ROOT / "docs" / "research" / "final_output_acceptance_error_log_2026_07_04.md" + + +def _latest_packet() -> tuple[int, Path, dict]: + sync_status.main() + packets: list[tuple[int, Path]] = [] + for path in WORK_DIR.glob("jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v*.json"): + match = re.search(r"\.v(\d+)\.json$", path.name) + if match: + packets.append((int(match.group(1)), path)) + assert packets, "no versioned JHora master evidence packets found" + version, path = max(packets) + return version, path, json.loads(path.read_text(encoding="utf-8")) + + +def test_latest_master_packet_has_consistent_final_status_and_version_metadata() -> None: + version, path, packet = _latest_packet() + metadata = packet["metadata"] + + assert version >= 24 + assert packet["status"] == "final_output_v1" + assert metadata["status"] == "final_output_v1" + assert metadata["current_version"] == f"v{version}" + assert metadata["packet_version"] == f"v{version}" + assert metadata["canonical_packet"] == path.name + assert packet["structured_v13_final_integrated_report"]["status"] == "final_output_v1" + + +def test_latest_master_packet_links_all_final_report_artifacts() -> None: + _, _, packet = _latest_packet() + + required_sections = { + "structured_v21_raman_full_report_complete": ("source", "compiled-full-report-v1"), + "structured_v22_raman_full_report_pdf_artifact": ("pdf", "pdf-rendered-qa-pass"), + "structured_v23_raman_full_report_raw_data_appendix": ("source", "raw-data-appendix-v1"), + } + for section, (artifact_key, status) in required_sections.items(): + payload = packet[section] + assert payload["status"] == status + artifact = ROOT / payload[artifact_key] + assert artifact.exists(), f"missing artifact for {section}: {artifact}" + assert artifact.stat().st_size > 1000, f"artifact too small for {section}: {artifact}" + + pdf_payload = packet["structured_v22_raman_full_report_pdf_artifact"] + for rel_path in pdf_payload["qa_rendered_pages"]: + qa_page = WORK_DIR / rel_path + assert qa_page.exists(), f"missing PDF QA render page: {qa_page}" + assert qa_page.stat().st_size > 1000 + + +def test_status_ledger_points_to_latest_packet_and_has_fresh_next_step() -> None: + version, path, _ = _latest_packet() + ledger = (WORK_DIR / "evidence_packet_status_ledger_REDACTED_DATE_REDACTED_TIME.md").read_text( + encoding="utf-8" + ) + + assert path.name in ledger + assert f"`{path.name}` | active" in ledger + assert "Raman full report complete" in ledger + assert "Raman full report PDF" in ledger + assert "Raman full report raw data appendix" in ledger + assert "Chapter 04 Parashari Dasha layer - Vimshottari" not in ledger + assert "Acceptance/error-log gate" in ledger + assert f"v{version}" in ledger + + +def test_acceptance_error_log_records_known_failures_and_prevention_rules() -> None: + text = ERROR_LOG.read_text(encoding="utf-8") + + required_phrases = [ + "status metadata drift", + "canonical packet drift", + "stale next-step ledger", + "blank PDF artifact", + "read this log before editing", + "test_final_jhora_evidence_packet_acceptance.py", + ] + for phrase in required_phrases: + assert phrase in text + + +def test_sync_script_repairs_latest_packet_metadata_and_ledger(tmp_path, monkeypatch) -> None: + for version in (2, 10): + packet = { + "status": "final_output_v1", + "metadata": { + "status": "final_output_v1", + "current_version": "v1", + "packet_version": f"v{version}", + "canonical_packet": "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v1.json", + }, + "structured_v13_final_integrated_report": {"status": "final_output_v1"}, + } + (tmp_path / f"jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v{version}.json").write_text( + json.dumps(packet, ensure_ascii=False), + encoding="utf-8", + ) + ledger = tmp_path / "evidence_packet_status_ledger_REDACTED_DATE_REDACTED_TIME.md" + ledger.write_text( + "| Master evidence packet | `jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v2.json` | active | Current canonical structured packet. |\n", + encoding="utf-8", + ) + + monkeypatch.setattr(sync_status, "WORK_DIR", tmp_path) + + assert sync_status.main() == 0 + latest = json.loads( + (tmp_path / "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v10.json").read_text( + encoding="utf-8" + ) + ) + assert latest["metadata"]["current_version"] == "v10" + assert latest["metadata"]["packet_version"] == "v10" + assert ( + latest["metadata"]["canonical_packet"] + == "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v10.json" + ) + assert "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v10.json" in ledger.read_text( + encoding="utf-8" + ) diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py index 08b026ca..8e1c1d6d 100644 --- a/tests/test_frontend_productization.py +++ b/tests/test_frontend_productization.py @@ -1617,6 +1617,7 @@ def test_web_entry_prefers_unified_consultation_workflow() -> None: bridge = read("api-bridge.js") public_bridge = read("public/api-bridge.js") main = read("main.js") + html = read("index.html") for source in (bridge, public_bridge): assert "computeConsultationWorkflow" in source @@ -1624,6 +1625,10 @@ def test_web_entry_prefers_unified_consultation_workflow() -> None: assert "entryMode = options.entryMode || 'direct_chart'" in main assert "entryMode: 'rectification'" in main + assert "entryMode: 'prashna'" in main + assert "run_muhurta_panchanga" in main + for token in ('id="entry-direct-chart"', 'id="entry-rectification"', 'id="entry-prashna"'): + assert token in html def test_result_page_surfaces_workflow_summary_and_provenance_detail() -> None: @@ -1636,6 +1641,12 @@ def test_result_page_surfaces_workflow_summary_and_provenance_detail() -> None: "renderWorkflowSummaryPanel", "renderWorkflowSummaryCard", "renderWorkflowProvenancePanel", + "runtime_truth", + "interpretation_source_runtime_coverage", + "official_execution_layers", + "runtime_visibility_status", + "VedAstro Runtime Truth", + "Interpretation Source Coverage", "runtime_planner", "renderRuntimePlannerPills", "UnifiedConsultationRuntimePlanner", @@ -2940,3 +2951,27 @@ def test_local_frontend_and_api_runtime_smoke() -> None: finally: stop_process(web) stop_process(api) + + +def test_unified_prashna_and_audited_remedies_are_rendered_from_consultation_workflow() -> None: + main = read("main.js") + for token in ( + "renderWorkflowSidecarsSummary", + "workflow-sidecars-summary", + "bindWorkflowSummaryActions", + "data-open-tab", + "data-scroll-target", + "switchToTab(tabName)", + "switchToTab('prashna')", + "switchToTab('remedies')", + "switchToTab('provenance')", + "workflow?.audited_remedies", + "workflow?.prashna", + "workflow?.muhurta_panchanga", + "Muhurta / Panchanga", + "Prashna 问事", + "补救建议", + "renderPrashnaResult(workflow.prashna", + "strict_audit_gate", + ): + assert token in main diff --git a/tests/test_interpretation_source_advanced_pipeline_contract.py b/tests/test_interpretation_source_advanced_pipeline_contract.py index a34fedcb..0f706f47 100644 --- a/tests/test_interpretation_source_advanced_pipeline_contract.py +++ b/tests/test_interpretation_source_advanced_pipeline_contract.py @@ -178,10 +178,10 @@ def test_prompt_pack_frontend_and_remaining_batch_queue_expose_next_stage_contra assert snapshot["real_case_calibration_layer"]["status"] == "queued" assert snapshot["technical_debt_contract"]["tajika"]["status"] == "partial" assert snapshot["remaining_priority1_batch_queue"]["next_batches"] == [ - "real_case_studies_batch1", - "rishi_ai_mcp_batch1", - "vedic_astro_skills_batch1", "references_batch2", + "vedastro_official_default_closure", + "external_oracle_parity_batch", + "install_usage_path_slimming", ] main_js = (ROOT / "jyotish-app" / "main.js").read_text(encoding="utf-8") diff --git a/tests/test_interpretation_source_inventory_gate.py b/tests/test_interpretation_source_inventory_gate.py index ee456dfc..2105d883 100644 --- a/tests/test_interpretation_source_inventory_gate.py +++ b/tests/test_interpretation_source_inventory_gate.py @@ -71,6 +71,19 @@ def test_quality_gate_runs_interpretation_source_inventory_gate() -> None: assert '"scripts/interpretation_source_inventory_gate.py"' in quality_gate assert '[PYTHON, "scripts/interpretation_source_inventory_gate.py"]' in quality_gate + assert '"scripts/sync_final_evidence_packet_status.py"' in quality_gate + assert '[PYTHON, "scripts/sync_final_evidence_packet_status.py"]' in quality_gate + assert '"scripts/diagnose_external_engine_adapters.py"' in quality_gate + assert '[PYTHON, "scripts/diagnose_external_engine_adapters.py", "--json"]' in quality_gate + assert '"runtime-truth"' in quality_gate + assert "tests/test_interpretation_source_runtime_coverage.py" in quality_gate + assert "tests/test_final_jhora_evidence_packet_acceptance.py" in quality_gate + assert ( + 'elif args.profile == "runtime-truth":\n' + " pytest_targets = RUNTIME_TRUTH_PYTEST_TARGETS\n" + " else:\n" + " pytest_targets = CORE_PYTEST_TARGETS" + ) in quality_gate def test_interpretation_source_inventory_gate_classifies_full_candidate_pool() -> None: diff --git a/tests/test_interpretation_source_next_phase_contract.py b/tests/test_interpretation_source_next_phase_contract.py index 4d568291..858b1e29 100644 --- a/tests/test_interpretation_source_next_phase_contract.py +++ b/tests/test_interpretation_source_next_phase_contract.py @@ -199,3 +199,68 @@ def test_prompt_pack_and_real_reading_regression_expose_content_contracts() -> N assert audit["mevg_global_web_evidence"]["status"] == "blocked" assert audit["real_case_calibration"]["status"] == "blocked" assert audit["interpretation_source_pack"]["core_rule_source_refs"] == CORE5 + + +def test_real_case_studies_batch1_is_exposed_as_local_retrieval_layer() -> None: + source_pack = _existing_interpretation_source_pack() + case_layer = source_pack["real_case_calibration_layer"] + assert case_layer["batch_id"] == "real_case_studies_batch1" + assert case_layer["index_status"] == "available" + assert case_layer["status"] == "queued" + assert "career" in case_layer["case_index_by_domain"] + assert "finance" in case_layer["case_index_by_domain"] + assert "relationship" in case_layer["case_index_by_domain"] + assert ( + "references/real_case_studies/vedicka/career-success-poverty-prosperity.md" + in case_layer["case_index_by_domain"]["career"] + ) + assert ( + "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json" + in case_layer["case_index_by_domain"]["relationship"] + ) + assert ( + "references/real_case_studies/vedicka/career-success-poverty-prosperity.md" + in source_pack["source_refs"] + ) + assert source_pack["real_case_calibration"]["local_index_status"] == "available" + assert source_pack["real_case_calibration"]["status"] == "blocked" + + +def test_open_source_batches_and_external_gaps_are_visible_without_polluting_truth() -> None: + source_pack = _existing_interpretation_source_pack() + + rishi_layer = source_pack["rishi_ai_mcp_batch1_layer"] + assert rishi_layer["status"] == "available" + assert rishi_layer["promotion_status"] == "open_source_reference_layer" + assert rishi_layer["runtime_truth_status"] == "not_primary_truth" + assert "career" in rishi_layer["domain_map"] + assert "relationship" in rishi_layer["domain_map"] + assert "references/open_source_sources/rishi-ai-mcp/.agents/rules/rishi-ai.md" in rishi_layer["source_refs"] + assert ( + "references/open_source_sources/rishi-ai-mcp/.agents/workflows/career-analysis.md" + in rishi_layer["domain_map"]["career"] + ) + + vedic_layer = source_pack["vedic_astro_skills_batch1_layer"] + assert vedic_layer["status"] == "available" + assert vedic_layer["promotion_status"] == "external_skill_reference_layer" + assert vedic_layer["runtime_truth_status"] == "not_primary_truth" + assert "calculator" in vedic_layer["domain_map"] + assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/SKILL.md" in vedic_layer["source_refs"] + assert ( + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/data_contract.md" + in vedic_layer["domain_map"]["reader_validation"] + ) + + external_gaps = source_pack["external_closure_gap_layer"] + assert external_gaps["vedastro_official"]["status"] == "blocked" + assert external_gaps["oracle_parity"]["status"] == "blocked" + assert external_gaps["install_usage_path"]["status"] == "needs_slimming" + + queue = source_pack["remaining_priority1_batch_queue"] + assert queue["next_batches"] == [ + "references_batch2", + "vedastro_official_default_closure", + "external_oracle_parity_batch", + "install_usage_path_slimming", + ] diff --git a/tests/test_interpretation_source_runtime_coverage.py b/tests/test_interpretation_source_runtime_coverage.py new file mode 100644 index 00000000..f7c4b40b --- /dev/null +++ b/tests/test_interpretation_source_runtime_coverage.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Regression tests for interpretation source runtime coverage report.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def test_interpretation_source_runtime_coverage_reports_machine_checkable_gap() -> None: + completed = subprocess.run( + [ + sys.executable, + "scripts/interpretation_source_runtime_coverage.py", + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=120, + check=False, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + assert report["scope"] == "interpretation_source_runtime_coverage" + assert report["status"] == "partial" + assert report["source_pack_status"] == "used" + assert "dasha_timing_layer_used" in report["proven_runtime_markers"] + assert "references/open_source_sources/jyotishganit" in report["not_fully_closed"] + assert report["inventory_gate"]["status"] == "pass" diff --git a/tests/test_jyotishganit_adapter_diagnostics.py b/tests/test_jyotishganit_adapter_diagnostics.py new file mode 100644 index 00000000..40e4d018 --- /dev/null +++ b/tests/test_jyotishganit_adapter_diagnostics.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def _run_diag(env: dict[str, str] | None = None) -> dict: + completed = subprocess.run( + [sys.executable, "scripts/diagnose_jyotishganit_adapter.py", "--json"], + cwd=ROOT, + text=True, + capture_output=True, + timeout=30, + check=False, + env={**os.environ, **(env or {})}, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout + return json.loads(completed.stdout) + + +def test_jyotishganit_adapter_diagnostics_reports_current_status() -> None: + report = _run_diag() + + assert report["scope"] == "jyotishganit_adapter_diagnostics" + assert report["adapter_path"] == "references/open_source_sources/jyotishganit" + assert report["status"] in {"available", "missing_checkout", "runtime_error"} + assert report["license"] == "MIT" + + +def test_jyotishganit_adapter_diagnostics_reports_missing_checkout() -> None: + report = _run_diag({"JYOTISHGANIT_ADAPTER_PATH": "references/open_source_sources/__missing_jyotishganit__"}) + + assert report["status"] == "missing_checkout" diff --git a/tests/test_marriage_precision_extensions.py b/tests/test_marriage_precision_extensions.py new file mode 100644 index 00000000..d099f115 --- /dev/null +++ b/tests/test_marriage_precision_extensions.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import sys +from datetime import datetime +from pathlib import Path + + +SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" +if str(SCRIPTS) not in sys.path: + sys.path.insert(0, str(SCRIPTS)) + +from jaimini import calc_darapada # noqa: E402 +from kp_system import calc_kp_analysis, calc_kp_dba_timeline # noqa: E402 + + +USER_PLANET_LONS = { + "Sun": 3.5036111111111112, + "Moon": 311.8041666666667, + "Mars": 91.31277777777778, + "Mercury": 338.52805555555557, + "Jupiter": 163.81833333333333, + "Venus": 340.5394444444444, + "Saturn": 304.28305555555556, + "Rahu": 231.02916666666667, + "Ketu": 51.028888888888886, +} + +USER_PLANET_HOUSES = { + "Sun": 9, + "Moon": 7, + "Mars": 12, + "Mercury": 8, + "Jupiter": 2, + "Venus": 8, + "Saturn": 7, + "Rahu": 4, + "Ketu": 10, +} + + +def test_calc_darapada_returns_a7_relationship_maintenance_fields(): + darapada = calc_darapada(4, USER_PLANET_LONS) + + assert darapada["name"] == "Darapada (A7)" + assert darapada["source_house_num"] == 7 + assert darapada["sign"] == "Scorpio" + assert darapada["second_from_a7"] == "Sagittarius" + assert darapada["eighth_from_a7"] == "Gemini" + + +def test_kp_dba_timeline_scores_marriage_houses(): + signs = [ + "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", + "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces", + ] + planet_positions = { + planet: { + "longitude": longitude, + "sign": signs[int(longitude // 30)], + "degree": longitude % 30, + "house": USER_PLANET_HOUSES[planet], + } + for planet, longitude in USER_PLANET_LONS.items() + } + kp = calc_kp_analysis(planet_positions, "Leo") + planet_significators = { + planet: data["significators"] + for planet, data in kp["planets"].items() + } + + timeline = calc_kp_dba_timeline( + datetime(REDACTED_YEAR, 4, 17, 14, 49), + 311.8041666666667, + datetime(2027, 1, 1), + datetime(2029, 12, 31), + planet_significators, + ) + + assert timeline["birth_star_lord"] == "Rahu" + assert timeline["target_start"].startswith("2027-01-01") + assert timeline["periods"] + assert any(row["judgement"] == "mixed" for row in timeline["periods"]) + assert all({"md_lord", "ad_lord", "pd_lord", "start", "end", "marriage_score"} <= set(row) for row in timeline["periods"]) diff --git a/tests/test_mcp_strict_workflow_career.py b/tests/test_mcp_strict_workflow_career.py index 4c04edfa..7d09bde8 100644 --- a/tests/test_mcp_strict_workflow_career.py +++ b/tests/test_mcp_strict_workflow_career.py @@ -4,9 +4,22 @@ from __future__ import annotations import jyotish_engine +import mcp_server from mcp_server import _collect_strict_evidence, _existing_interpretation_source_pack +SOURCE_LAYER_CONTEXT = { + "dasha_timing_layer_used", + "varga_strength_layer_used", + "annual_special_layer_context", + "modifier_obstacle_layer_used", +} + + +def _assert_context_contains(context: list[str], expected: set[str]) -> None: + assert expected <= set(context) + assert SOURCE_LAYER_CONTEXT <= set(context) + def _base_career_result() -> dict: return { @@ -62,14 +75,17 @@ def test_career_collects_a10_amk_karakamsha_as_strict_evidence() -> None: } assert strict["event_judgement"]["event_family"] == "career" assert strict["event_judgement"]["dominant_label"] == "career_status" - assert strict["event_judgement"]["secondary_context"] == [ - "a10_active", - "amk_active", - "karakamsha_context", - "functional_benefic_malefic_used", - "argala_support", - "vedastro_range_scan_missing", - ] + _assert_context_contains( + strict["event_judgement"]["secondary_context"], + { + "a10_active", + "amk_active", + "karakamsha_context", + "functional_benefic_malefic_used", + "argala_support", + "vedastro_range_scan_missing", + }, + ) def test_career_strict_contract_attaches_existing_interpretation_source_pack() -> None: @@ -120,6 +136,54 @@ def test_interpretation_source_inventory_classifies_sources_without_promoting_dr assert all(path not in source_pack["source_refs"] for path in draft_refs) +def test_mcp_strict_workflow_returns_runtime_evidence_log(monkeypatch) -> None: + def fake_execute(**kwargs): + return { + "chart": { + "modules": {}, + "ai_prompt_pack": { + "evidence_snapshot": { + "vedastro_official_snapshot": { + "status": "ok", + "official_primary_evidence": {"chart_core": {"status": "ok"}}, + } + } + }, + }, + "routing": {"question_type": "career", "primary_theme": "career"}, + "entry_mode": "direct_chart", + "runtime_planner": {"executed_steps": ["compute_chart"], "skipped_steps": []}, + } + + monkeypatch.setattr(mcp_server, "_execute_mcp_consultation_workflow", fake_execute) + monkeypatch.setattr(mcp_server, "_maybe_attach_vedastro_evidence", lambda route, chart, **kwargs: chart) + monkeypatch.setattr(mcp_server, "_collect_strict_evidence", lambda route, chart: {"question_type": route}) + + result = mcp_server.strict_workflow( + question="career timing", + year=REDACTED_YEAR, + month=4, + day=17, + hour=14, + minute=49, + lat=36.42, + lon=114.2, + tz=8, + age=33, + transit_date="2026-07-05", + ) + + assert result["runtime_evidence_log"]["surface"] == "skill_mcp" + assert result["runtime_evidence_log"]["route"]["question_type"] == "career" + assert result["runtime_evidence_log"]["vedastro_cloud_state"] == "official_verified" + assert result["machine_evidence_packet"]["status"] == "partial" + assert result["real_case_calibration"]["status"] == "partial_scored" + assert result["runtime_evidence_log"]["evidence_packet_contract"]["status"] == "partial" + assert result["runtime_evidence_log"]["real_case_calibration"]["status"] == "partial_scored" + assert result["runtime_evidence_log"]["quality_gate"]["technique_audit_table_required"] is True + assert result["runtime_evidence_log"]["quality_gate"]["technique_audit_table"][0]["technique"] == "VedAstro Cloud State" + + def test_career_blocks_label_when_d10_is_missing_but_preserves_jaimini_context() -> None: result = _base_career_result() del result["modules"]["varga_full"]["D10_Dasamsa"] @@ -129,14 +193,17 @@ def test_career_blocks_label_when_d10_is_missing_but_preserves_jaimini_context() assert "d10_dasamsa" in strict["missing_evidence"] assert strict["blocked"] is True assert strict["event_judgement"]["dominant_label"] is None - assert strict["event_judgement"]["secondary_context"] == [ - "a10_active", - "amk_active", - "karakamsha_context", - "functional_benefic_malefic_used", - "argala_support", - "vedastro_range_scan_missing", - ] + _assert_context_contains( + strict["event_judgement"]["secondary_context"], + { + "a10_active", + "amk_active", + "karakamsha_context", + "functional_benefic_malefic_used", + "argala_support", + "vedastro_range_scan_missing", + }, + ) def test_career_dignity_guardrail_uses_career_relevant_planets_only() -> None: diff --git a/tests/test_mcp_strict_workflow_finance.py b/tests/test_mcp_strict_workflow_finance.py index e6852a19..8f8ec3b0 100644 --- a/tests/test_mcp_strict_workflow_finance.py +++ b/tests/test_mcp_strict_workflow_finance.py @@ -12,6 +12,18 @@ from mcp_server import ( _derive_yogi_wealth_support, ) +SOURCE_LAYER_CONTEXT = { + "dasha_timing_layer_used", + "varga_strength_layer_used", + "annual_special_layer_context", + "modifier_obstacle_layer_used", +} + + +def _assert_context_contains(context: list[str], expected: set[str]) -> None: + assert expected <= set(context) + assert SOURCE_LAYER_CONTEXT <= set(context) + def test_finance_public_wealth_label_requires_at_least_moderate_window() -> None: judgement = _derive_event_judgement( @@ -31,7 +43,7 @@ def test_finance_public_wealth_label_requires_at_least_moderate_window() -> None assert judgement["verdict"] == "weak_window_needs_confirmation" assert judgement["payout_label"] is None assert judgement["dominant_label"] is None - assert judgement["secondary_context"] == [] + assert set(judgement["secondary_context"]) == SOURCE_LAYER_CONTEXT def test_finance_strict_contract_attaches_existing_interpretation_source_pack() -> None: @@ -72,7 +84,7 @@ def test_finance_public_wealth_label_can_lift_visible_wealth_cases() -> None: assert judgement["verdict"] == "moderate_probability_window" assert judgement["payout_label"] == "public_wealth_status" assert judgement["dominant_label"] == "public_wealth_status" - assert judgement["secondary_context"] == ["career_status", "gains_wishes"] + _assert_context_contains(judgement["secondary_context"], {"career_status", "gains_wishes"}) def test_finance_prefers_income_growth_when_gains_outrun_public_status_signals() -> None: @@ -92,7 +104,7 @@ def test_finance_prefers_income_growth_when_gains_outrun_public_status_signals() assert judgement["verdict"] == "moderate_probability_window" assert judgement["payout_label"] == "income_growth" assert judgement["dominant_label"] == "income_growth" - assert judgement["secondary_context"] == ["wealth_family"] + _assert_context_contains(judgement["secondary_context"], {"wealth_family"}) def test_finance_strong_wealth_promise_can_unlock_public_wealth_status() -> None: @@ -120,7 +132,7 @@ def test_finance_strong_wealth_promise_can_unlock_public_wealth_status() -> None assert judgement["verdict"] == "moderate_probability_window" assert judgement["payout_label"] == "public_wealth_status" assert judgement["dominant_label"] == "public_wealth_status" - assert judgement["secondary_context"] == ["career_status", "gains_wishes"] + _assert_context_contains(judgement["secondary_context"], {"career_status", "gains_wishes"}) def test_finance_source_diversity_adds_small_bump_without_changing_verdict_band() -> None: diff --git a/tests/test_mcp_strict_workflow_relationship.py b/tests/test_mcp_strict_workflow_relationship.py index e496f728..c0316659 100644 --- a/tests/test_mcp_strict_workflow_relationship.py +++ b/tests/test_mcp_strict_workflow_relationship.py @@ -14,6 +14,19 @@ sys.path.insert(0, str(ROOT / "scripts")) import jyotish_engine # noqa: E402 +SOURCE_LAYER_CONTEXT = { + "dasha_timing_layer_used", + "varga_strength_layer_used", + "annual_special_layer_context", + "modifier_obstacle_layer_used", +} + + +def _assert_context_contains(context: list[str], expected: set[str]) -> None: + assert expected <= set(context) + assert SOURCE_LAYER_CONTEXT <= set(context) + + def _base_relationship_result() -> dict: return { "modules": { @@ -53,13 +66,16 @@ def test_relationship_jaimini_bridge_lifts_legal_marriage_label() -> None: "source": "jaimini_bridge_v1", } assert strict["event_judgement"]["dominant_label"] == "legal_marriage" - assert strict["event_judgement"]["secondary_context"] == [ - "darakaraka_active", - "jaimini_support", - "ul_support", - "virodhargala_obstruction", - "vedastro_range_scan_missing", - ] + _assert_context_contains( + strict["event_judgement"]["secondary_context"], + { + "darakaraka_active", + "jaimini_support", + "ul_support", + "virodhargala_obstruction", + "vedastro_range_scan_missing", + }, + ) def test_relationship_strict_contract_attaches_existing_interpretation_source_pack() -> None: @@ -95,13 +111,16 @@ def test_relationship_jaimini_bridge_stays_context_only_when_d9_missing() -> Non "source": "jaimini_bridge_v1", } assert strict["event_judgement"]["dominant_label"] is None - assert strict["event_judgement"]["secondary_context"] == [ - "darakaraka_active", - "jaimini_support", - "ul_support", - "virodhargala_obstruction", - "vedastro_range_scan_missing", - ] + _assert_context_contains( + strict["event_judgement"]["secondary_context"], + { + "darakaraka_active", + "jaimini_support", + "ul_support", + "virodhargala_obstruction", + "vedastro_range_scan_missing", + }, + ) def test_relationship_jaimini_bridge_cannot_lift_legal_marriage_when_narayana_is_missing() -> None: diff --git a/tests/test_muhurta.py b/tests/test_muhurta.py index 81709143..80d563ff 100644 --- a/tests/test_muhurta.py +++ b/tests/test_muhurta.py @@ -13,6 +13,7 @@ from muhurta import ( calc_vara, calc_hora, calc_abhijit_muhurta, calc_panchanga, check_activity_muhurta, muhurta_full_report, calc_daytime_inauspicious_periods, panchanga_range_report, muhurta_range_search, calc_sunrise_sunset_local, calc_panchanga_end_times, + build_muhurta_sidecar, classify_vrata_tags, classify_panchanga_condition_tags, calc_choghadiya_windows, calc_hora_windows, TITHI_NAMES, TITHI_QUALITY, NAKSHATRAS, NAKSHATRA_TYPE, YOGA_NAMES, YOGA_QUALITY, KARANA_NAMES, KARANA_QUALITY, @@ -410,3 +411,21 @@ class TestMuhurtaRangeSearch: assert {'date', 'score', 'quality', 'activity_verdict', 'recommended_windows', 'evidence'} <= set(first) assert first['recommended_windows'] assert first['evidence']['panchanga'] + + +class TestMuhurtaSidecar: + def test_build_muhurta_sidecar_returns_compact_workflow_shape(self): + result = build_muhurta_sidecar( + date_str='2026-07-08', + activity='business', + lat=36.42, + lon=114.2, + tz=8, + ) + assert result['status'] == 'ok' + assert result['source'] == 'local_muhurta.py' + assert result['activity'] == 'business' + assert result['report_mode'] == 'muhurta_date_range_solver' + assert result['panchanga']['query_date'] == '2026-07-08' + assert result['best_windows'] + assert 'next_action' in result diff --git a/tests/test_pre_work_check.py b/tests/test_pre_work_check.py new file mode 100644 index 00000000..035bc3a8 --- /dev/null +++ b/tests/test_pre_work_check.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from scripts.pre_work_check import ( + DEFAULT_COMMAND_TIMEOUT_SECONDS, + DEFAULT_FRAGMENT_TIMEOUT_SECONDS, + EXTERNAL_ENGINE_DIAGNOSTIC_TARGET, + FOCUSED_TEST_TARGETS, + PRE_WORK_DOCS, + classify_status, +) + + +def test_classify_status_keeps_remote_blocked_distinct_from_failure() -> None: + assert classify_status(True, True, True, "verified") == "pass" + assert classify_status(True, True, True, "blocked") == "pass_with_remote_blocked" + assert classify_status(True, True, False, "verified") == "fail" + + +def test_pre_work_check_runs_governance_test_set() -> None: + assert "tests/test_runtime_import_boundaries.py" in FOCUSED_TEST_TARGETS + assert "tests/test_project_fragment_governance.py" in FOCUSED_TEST_TARGETS + assert "tests/test_preflight_fragment_scan.py" in FOCUSED_TEST_TARGETS + assert "tests/test_remote_repo_visibility_check.py" in FOCUSED_TEST_TARGETS + assert "tests/test_pre_work_check.py" in FOCUSED_TEST_TARGETS + + +def test_pre_work_check_requires_error_ledger_and_fragment_sweeps() -> None: + assert "docs/research/pre_work_error_ledger.md" in PRE_WORK_DOCS + assert "docs/research/whole_machine_fragment_sweep_2026_07_05.md" in PRE_WORK_DOCS + assert "docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md" in PRE_WORK_DOCS + + +def test_pre_work_check_includes_external_engine_diagnostics() -> None: + assert EXTERNAL_ENGINE_DIAGNOSTIC_TARGET == "scripts/diagnose_external_engine_adapters.py" + + +def test_pre_work_check_child_command_timeout_stays_short() -> None: + assert DEFAULT_COMMAND_TIMEOUT_SECONDS <= 45 + assert DEFAULT_FRAGMENT_TIMEOUT_SECONDS >= DEFAULT_COMMAND_TIMEOUT_SECONDS diff --git a/tests/test_preflight_fragment_scan.py b/tests/test_preflight_fragment_scan.py index db26fccf..10308d10 100644 --- a/tests/test_preflight_fragment_scan.py +++ b/tests/test_preflight_fragment_scan.py @@ -4,15 +4,22 @@ from __future__ import annotations import json +import os import subprocess import sys from pathlib import Path +import pytest + ROOT = Path(__file__).resolve().parents[1] -def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets() -> None: +@pytest.fixture(scope="module") +def report() -> dict: + cached_report = os.environ.get("PREFLIGHT_FRAGMENT_SCAN_REPORT") + if cached_report and Path(cached_report).exists(): + return json.loads(Path(cached_report).read_text(encoding="utf-8")) completed = subprocess.run( [ sys.executable, @@ -24,10 +31,11 @@ def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets() -> timeout=120, check=False, ) - assert completed.returncode == 0, completed.stderr or completed.stdout - report = json.loads(completed.stdout) + return json.loads(completed.stdout) + +def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets(report: dict) -> None: assert report["scope"] == "preflight_fragment_scan" assert report["summary"]["authority_layers_scanned"] == 4 assert report["summary"]["production_truth_layer"] == "main_repo_truth" @@ -59,22 +67,7 @@ def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets() -> assert "historical_event_accuracy_not_proven" in risk_ids -def test_preflight_fragment_scan_preserves_audit_capability_and_oracle_boundaries() -> None: - completed = subprocess.run( - [ - sys.executable, - "scripts/preflight_fragment_scan.py", - ], - cwd=ROOT, - text=True, - capture_output=True, - timeout=120, - check=False, - ) - - assert completed.returncode == 0, completed.stderr or completed.stdout - report = json.loads(completed.stdout) - +def test_preflight_fragment_scan_preserves_audit_capability_and_oracle_boundaries(report: dict) -> None: audit = report["upstream_audits"] assert audit["capability_audit"]["valid"] is True assert audit["capability_audit"]["technique_count"] >= 89 @@ -96,22 +89,7 @@ def test_preflight_fragment_scan_preserves_audit_capability_and_oracle_boundarie assert any(".workbuddy/skills/jyotish-vedic-astrology" in layer for layer in cleanup_map["focus_layers"]) -def test_preflight_fragment_scan_emits_repo_cleanup_priorities() -> None: - completed = subprocess.run( - [ - sys.executable, - "scripts/preflight_fragment_scan.py", - ], - cwd=ROOT, - text=True, - capture_output=True, - timeout=120, - check=False, - ) - - assert completed.returncode == 0, completed.stderr or completed.stdout - report = json.loads(completed.stdout) - +def test_preflight_fragment_scan_emits_repo_cleanup_priorities(report: dict) -> None: priorities = report["cleanup_priorities"] assert len(priorities) >= 2 assert all(item["id"] != "remove_mirror_runtime_dependency" for item in priorities) @@ -120,82 +98,13 @@ def test_preflight_fragment_scan_emits_repo_cleanup_priorities() -> None: assert any(item["id"] == "promote_or_archive_high_value_drafts" for item in priorities) -def test_preflight_fragment_scan_excludes_promoted_first_five_drafts_from_unpromoted_pool() -> None: - completed = subprocess.run( - [ - sys.executable, - "scripts/preflight_fragment_scan.py", - ], - cwd=ROOT, - text=True, - capture_output=True, - timeout=120, - check=False, - ) - - assert completed.returncode == 0, completed.stderr or completed.stdout - report = json.loads(completed.stdout) - unpromoted_paths = {item["path"] for item in report["high_value_unpromoted"]} - - blocked_names = { - "antigravity_round36_tajika_sahams_external_closure_pack_2026_06_26.md", - "antigravity_round37_dasha_external_oracle_shortest_closure_board_2026_06_26.md", - "antigravity_round37_shadbala_absolute_value_frontier_board_2026_06_26.md", - "antigravity_round39_yogi_wealth_bridge_audit_2026_06_28.md", - "three_fronts_skill_depth_audit_2026_06_26.md", - } - assert all(not any(name in path for path in unpromoted_paths) for name in blocked_names) - - -def test_preflight_fragment_scan_excludes_promoted_third_batch_drafts_from_unpromoted_pool() -> None: - completed = subprocess.run( - [ - sys.executable, - "scripts/preflight_fragment_scan.py", - ], - cwd=ROOT, - text=True, - capture_output=True, - timeout=120, - check=False, - ) - - assert completed.returncode == 0, completed.stderr or completed.stdout - report = json.loads(completed.stdout) - unpromoted_paths = {item["path"] for item in report["high_value_unpromoted"]} - - blocked_names = { - "antigravity_round36_asc_degree_yogi_tight_orb_wealth_pack_2026_06_26.md", - "antigravity_round37_tajika_sahams_annual_closure_board_2026_06_26.md", - "antigravity_round38_whole_machine_fragment_reuse_sixth_pass_2026_06_26.md", - "antigravity_round40_shadbala_absolute_authority_ladder_2026_06_27.md", - "antigravity_round40_tajika_annual_second_wave_board_2026_06_27.md", - } - assert all(not any(name in path for path in unpromoted_paths) for name in blocked_names) - - -def test_preflight_fragment_scan_excludes_promoted_fourth_batch_drafts_from_unpromoted_pool() -> None: - completed = subprocess.run( - [ - sys.executable, - "scripts/preflight_fragment_scan.py", - ], - cwd=ROOT, - text=True, - capture_output=True, - timeout=120, - check=False, - ) - - assert completed.returncode == 0, completed.stderr or completed.stdout - report = json.loads(completed.stdout) - unpromoted_paths = {item["path"] for item in report["high_value_unpromoted"]} - - blocked_names = { - "antigravity_round40_whole_machine_fragment_reuse_shortlist_2026_06_27.md", - "dasha_accuracy_closure_status_2026_06_26.md", - "dasha_code_only_priority_rerank_2026_06_26.md", - "skill_fragment_map_and_source_of_truth_2026_06_26.md", - "skill_truth_conflict_matrix_2026_06_26.md", - } - assert all(not any(name in path for path in unpromoted_paths) for name in blocked_names) +def test_preflight_fragment_scan_links_pre_work_governance(report: dict) -> None: + governance = report["governance"] + assert governance["pre_work_error_ledger"]["exists"] is True + assert governance["pre_work_error_ledger"]["path"].endswith("pre_work_error_ledger.md") + assert governance["latest_fragment_sweep"]["exists"] is True + assert governance["latest_fragment_sweep"]["remote_ref_parity"] == "blocked_until_git_ls_remote_succeeds" + assert governance["prior_fragment_sweep"]["exists"] is True + assert "python3 -m pytest" in governance["acceptance_command"] + assert "tests/test_remote_repo_visibility_check.py" in governance["acceptance_command"] + assert "tests/test_pre_work_check.py" in governance["acceptance_command"] diff --git a/tests/test_project_fragment_governance.py b/tests/test_project_fragment_governance.py new file mode 100644 index 00000000..6f870725 --- /dev/null +++ b/tests/test_project_fragment_governance.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ERROR_LEDGER = ROOT / "docs" / "research" / "pre_work_error_ledger.md" +SWEEP = ROOT / "docs" / "research" / "whole_machine_fragment_sweep_2026_07_05.md" + + +def test_pre_work_error_ledger_exists_and_names_repeat_failures() -> None: + text = ERROR_LEDGER.read_text(encoding="utf-8") + required = [ + "ERR-001", + "ERR-005", + "ERR-007", + "ERR-009", + "ERR-017", + "ERR-018", + "tests/test_runtime_import_boundaries.py", + "tests/test_project_fragment_governance.py", + "docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md", + "scripts/diagnose_external_engine_adapters.py --json", + "docs/research/external_engine_blocker_research_2026_07_05.md", + ] + missing = [item for item in required if item not in text] + assert missing == [] + + +def test_agents_requires_pre_work_error_ledger() -> None: + text = (ROOT / "AGENTS.md").read_text(encoding="utf-8") + assert "docs/research/pre_work_error_ledger.md" in text + assert "开工前" in text + assert "python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45" in text + assert "scripts/diagnose_external_engine_adapters.py --json" in text + + +def test_fragment_sweep_records_main_mirror_and_remote_boundaries() -> None: + text = SWEEP.read_text(encoding="utf-8") + required = [ + "/Users/wuyongnaren/Documents/印度占星", + "/Users/wuyongnaren/.workbuddy/skills/jyotish-vedic-astrology", + "/Users/wuyongnaren/Documents/星轨talk/engines-repo/jyotish", + "github.com:732642856/yinduzhanxing.git", + "SSL_ERROR_SYSCALL", + "terminal ref parity is `blocked`", + ] + missing = [item for item in required if item not in text] + assert missing == [] + + +def test_error_ledger_contains_split_scan_commands_not_unbounded_home_scan() -> None: + text = ERROR_LEDGER.read_text(encoding="utf-8") + assert "-maxdepth 6" in text + assert "-maxdepth 7" in text + assert "find /Users/wuyongnaren -type" not in text diff --git a/tests/test_pyjhora_adapter_diagnostics.py b/tests/test_pyjhora_adapter_diagnostics.py new file mode 100644 index 00000000..c28ca8f5 --- /dev/null +++ b/tests/test_pyjhora_adapter_diagnostics.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def _run_diag(env: dict[str, str] | None = None) -> dict: + completed = subprocess.run( + [sys.executable, "scripts/diagnose_pyjhora_adapter.py", "--json"], + cwd=ROOT, + text=True, + capture_output=True, + timeout=30, + check=False, + env={**os.environ, **(env or {})}, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout + return json.loads(completed.stdout) + + +def test_pyjhora_adapter_diagnostics_reports_current_status() -> None: + report = _run_diag() + + assert report["scope"] == "pyjhora_adapter_diagnostics" + assert report["adapter_command"] == "python3 benchmarks/jyotish/scripts/run_pyjhora_compare.py" + assert report["status"] in {"available", "missing_dependency"} + + +def test_pyjhora_adapter_diagnostics_reports_missing_dependency() -> None: + report = _run_diag({"PYJHORA_MODULE_NAME": "__definitely_missing_pyjhora_module__"}) + + assert report["status"] == "missing_dependency" + assert report["missing_dependency"] == "__definitely_missing_pyjhora_module__" + + +def test_pyjhora_adapter_diagnostics_reports_actionable_external_boundary() -> None: + report = _run_diag({"PYJHORA_MODULE_NAME": "__definitely_missing_pyjhora_module__"}) + + assert report["install_hint"]["package"] == "PyJHora" + assert "pip install PyJHora" in report["install_hint"]["commands"] + assert report["license_boundary"] == "AGPL external benchmark only; do not vendor or make it a runtime dependency." + assert report["ephemeris_data_note"] diff --git a/tests/test_remote_repo_visibility_check.py b/tests/test_remote_repo_visibility_check.py new file mode 100644 index 00000000..d4ba2b8f --- /dev/null +++ b/tests/test_remote_repo_visibility_check.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from scripts.remote_repo_visibility_check import github_slug_from_remote_url, parse_ls_remote + + +def test_github_slug_from_remote_url_accepts_ssh_and_https() -> None: + assert github_slug_from_remote_url("git@github.com:732642856/yinduzhanxing.git") == "732642856/yinduzhanxing" + assert github_slug_from_remote_url("https://github.com/732642856/yinduzhanxing.git") == "732642856/yinduzhanxing" + + +def test_parse_ls_remote_separates_heads_and_tags() -> None: + parsed = parse_ls_remote( + "\n".join( + [ + "abc123 refs/heads/main", + "def456 refs/heads/codex/release-hygiene-ci", + "aaa111 refs/tags/v1.0.0", + "bbb222 refs/tags/v1.0.0^{}", + ] + ) + ) + assert parsed["heads"]["main"] == "abc123" + assert parsed["heads"]["codex/release-hygiene-ci"] == "def456" + assert parsed["tags"]["v1.0.0"] == "aaa111" + assert parsed["ref_count"] == 3 diff --git a/tests/test_unified_consultation_orchestrator.py b/tests/test_unified_consultation_orchestrator.py index 938e0a26..46f2defe 100644 --- a/tests/test_unified_consultation_orchestrator.py +++ b/tests/test_unified_consultation_orchestrator.py @@ -82,4 +82,232 @@ def test_unified_consultation_orchestrator_rectification_entry_runs_gate_first() assert planner["entry_mode"] == "rectification" assert planner["sync_steps"][0] == "run_rectification_gate" assert "compute_chart" in planner["sync_steps"] - assert "run_thematic_report" in planner["sync_steps"] + + +def test_unified_consultation_orchestrator_prashna_entry_runs_prashna_before_thematic() -> None: + orchestrator = UnifiedConsultationOrchestrator() + themes = orchestrator.normalize_themes(["wealth"]) + route = orchestrator.resolve_route("时间问事:这个合作能成吗", themes) + + planner = orchestrator.runtime_planner( + entry_mode="prashna", + question="时间问事:这个合作能成吗", + themes=themes, + route_packet=route, + events=[], + surface="api_web", + high_rigor=False, + ) + + assert planner["entry_mode"] == "prashna" + assert planner["sync_steps"][0] == "run_prashna" + assert "compute_chart" not in planner["sync_steps"] + + +def test_unified_consultation_orchestrator_timing_route_adds_muhurta_sidecar() -> None: + orchestrator = UnifiedConsultationOrchestrator() + themes = orchestrator.normalize_themes(["career"]) + route = orchestrator.resolve_route("2026年何时适合谈合作和推进项目的应期", themes) + planner = orchestrator.runtime_planner( + entry_mode="direct_chart", + question="2026年何时适合谈合作和推进项目的应期", + themes=themes, + route_packet=route, + events=[], + surface="api_web", + high_rigor=False, + ) + assert planner["route"]["question_type"] == "timing" + assert "run_muhurta_panchanga" in planner["sync_steps"] + assert planner["sync_steps"].index("run_muhurta_panchanga") < planner["sync_steps"].index("run_thematic_report") + + +def test_unified_consultation_orchestrator_prefers_timing_when_career_question_asks_when() -> None: + orchestrator = UnifiedConsultationOrchestrator() + themes = orchestrator.normalize_themes(["career"]) + route = orchestrator.resolve_route("2026年什么时候会有事业机会", themes) + assert route["question_type"] == "timing" + + +def test_runtime_evidence_log_classifies_official_verified_local_fallback_and_blocked() -> None: + orchestrator = UnifiedConsultationOrchestrator() + route = {"question_type": "career", "primary_theme": "career"} + + verified = orchestrator.runtime_evidence_log( + surface="api_web", + entry_mode="direct_chart", + route_packet=route, + executed_steps=["compute_chart"], + skipped_steps=[], + vedastro_official={ + "runtime_truth": { + "status": "ok", + "official_execution_layers": {"chart_core": "ok"}, + "fallback_active": False, + }, + "raw_response": {"source": "official"}, + }, + ) + assert verified["vedastro_cloud_state"] == "official_verified" + + fallback = orchestrator.runtime_evidence_log( + surface="skill_mcp", + entry_mode="direct_chart", + route_packet=route, + executed_steps=["compute_chart"], + skipped_steps=[], + vedastro_official={"runtime_truth": {"status": "network_execution_disabled", "fallback_active": True}}, + ) + assert fallback["vedastro_cloud_state"] == "local_fallback" + + blocked = orchestrator.runtime_evidence_log( + surface="skill_mcp", + entry_mode="direct_chart", + route_packet=route, + executed_steps=[], + skipped_steps=["compute_chart"], + vedastro_official={"runtime_truth": {"status": "service_endpoint_not_configured", "fallback_active": False}}, + ) + assert blocked["vedastro_cloud_state"] == "official_blocked" + + +def test_runtime_evidence_log_exposes_blind_packet_case_and_quality_gate_contracts() -> None: + orchestrator = UnifiedConsultationOrchestrator() + log = orchestrator.runtime_evidence_log( + surface="api_web", + entry_mode="direct_chart", + route_packet={"question_type": "finance", "primary_theme": "wealth"}, + executed_steps=["compute_chart", "run_thematic_report"], + skipped_steps=["run_historical_event_backtest"], + vedastro_official={"runtime_truth": {"status": "partial", "fallback_active": True}}, + blind=True, + ) + + assert log["blind_technical_mode"]["enabled"] is True + assert "conversation_feedback" in log["blind_technical_mode"]["disallowed_sources"] + assert log["evidence_packet_contract"]["required_sections"][:5] == ["D1", "D9", "D10", "D2", "D4"] + assert "external_oracle_status" in log["evidence_packet_contract"]["required_sections"] + assert log["real_case_calibration"]["status"] == "required_not_satisfied" + assert log["quality_gate"]["technique_audit_table_required"] is True + assert [row["technique"] for row in log["quality_gate"]["technique_audit_table"]] == [ + "VedAstro Cloud State", + "External Engine Cross-Validation", + "Evidence Packet", + "Blind Technical Mode", + "MEVG / Global Web Evidence", + "Real Case Calibration", + "Functional Benefic/Malefic", + ] + engines = log["external_engine_cross_validation"]["engines"] + assert engines["VedAstro"]["status"] == "local_fallback" + assert engines["PyJHora/JHora"]["status"] == "reference_available_not_runtime_invoked" + assert engines["PyJHora/JHora"]["adapter_command"] == "python3 benchmarks/jyotish/scripts/run_pyjhora_compare.py" + assert engines["PyJHora/JHora"]["adapter_status"] in { + "available", + "blocked_missing_python_module:jhora", + } + assert engines["jyotishganit"]["status"] == "reference_available_not_runtime_invoked" + assert engines["jyotishganit"]["adapter_path"] == "references/open_source_sources/jyotishganit" + assert engines["jyotishganit"]["adapter_status"] == "available" + assert engines["jyotishganit"]["license"] == "MIT" + assert log["external_engine_cross_validation"]["status"] == "partial" + assert log["quality_gate"]["blocked_items"] + + packet = orchestrator.machine_evidence_packet( + chart={"chart": {"planets": {"Sun": {}}, "ascendant": {"sign": "Leo"}}}, + route_packet={"question_type": "career", "primary_theme": "career"}, + ) + log_with_functional = orchestrator.runtime_evidence_log( + surface="api_web", + entry_mode="direct_chart", + route_packet={"question_type": "career", "primary_theme": "career"}, + executed_steps=["compute_chart"], + skipped_steps=[], + machine_evidence_packet=packet, + ) + functional_row = log_with_functional["quality_gate"]["technique_audit_table"][-1] + assert functional_row["technique"] == "Functional Benefic/Malefic" + assert functional_row["status"] == "used" + assert "Mars" in functional_row["yogakarakas"] + + +def test_machine_evidence_packet_materializes_required_section_statuses() -> None: + orchestrator = UnifiedConsultationOrchestrator() + packet = orchestrator.machine_evidence_packet( + chart={ + "chart": { + "planets": {"Sun": {"lon": 12.3}}, + "ascendant": {"lon": 91.2, "sign": "Leo"}, + "houses": {"1": {"lon": 91.2}}, + }, + "modules": { + "varga_full": {"D9_Navamsa": {}, "D10_Dasamsa": {"summary": "present"}}, + "dasha": {"current": "Saturn"}, + "shadbala": {"Sun": 1.0}, + }, + "special_lagnas": {"UL": {"sign": "Capricorn"}, "A10_Karma_Pada": {"sign": "Aries"}}, + }, + route_packet={"question_type": "career", "primary_theme": "career"}, + vedastro_official={ + "runtime_truth": { + "status": "ok", + "official_execution_layers": {"chart_core": "ok"}, + "fallback_active": False, + }, + "raw_response": {"source": "official"}, + }, + ) + + assert packet["status"] == "partial" + assert packet["sections"]["D1"]["status"] == "used" + assert packet["sections"]["D10"]["status"] == "used" + assert packet["sections"]["functional_benefic_malefic"]["status"] == "used" + assert packet["functional_benefic_malefic"]["ascendant"] == "Leo" + assert "Mars" in packet["functional_benefic_malefic"]["yogakarakas"] + assert packet["sections"]["external_oracle_status"]["status"] == "official_verified" + assert packet["sections"]["vedastro_official_raw_response"]["status"] == "used" + assert "D2" in packet["missing_sections"] + + +def test_machine_evidence_packet_requires_vedastro_raw_response_for_official_closure() -> None: + packet = UnifiedConsultationOrchestrator().machine_evidence_packet( + chart={"chart": {"planets": {"Sun": {}}, "ascendant": {"sign": "Leo"}}}, + route_packet={"question_type": "career"}, + vedastro_official={ + "runtime_truth": { + "status": "ok", + "official_execution_layers": {"chart_core": "ok"}, + "fallback_active": False, + } + }, + ) + + assert packet["sections"]["external_oracle_status"]["status"] == "official_verified" + assert packet["sections"]["vedastro_official_raw_response"]["status"] == "missing" + assert "vedastro_official_raw_response" in packet["missing_sections"] + + +def test_real_case_calibration_catalog_exposes_local_sources_without_claiming_match() -> None: + orchestrator = UnifiedConsultationOrchestrator() + packet = orchestrator.machine_evidence_packet( + chart={ + "chart": {"planets": {"Venus": {"lon": 1}}, "ascendant": {"lon": 2}}, + "modules": {"varga_full": {"D9_Navamsa": {"summary": "present"}}, "dasha": {"current": "Venus"}}, + "special_lagnas": {"UL": {"sign": "Cancer"}}, + }, + route_packet={"question_type": "relationship", "primary_theme": "marriage"}, + ) + catalog = orchestrator.real_case_calibration_catalog( + route_packet={"question_type": "relationship", "primary_theme": "marriage"}, + machine_evidence_packet=packet, + ) + + assert catalog["status"] == "partial_scored" + assert catalog["batch_id"] == "real_case_studies_batch1" + assert "relationship" in catalog["case_index_by_domain"] + assert catalog["reference_grade"] == "partial_reference" + assert catalog["scored_candidates"][0]["event_trigger_match"]["status"] == "partial_match_official_timing_blocked" + assert catalog["scored_candidates"][0]["event_trigger_match"]["checks"]["dasha_boundaries"] == "used" + assert catalog["scored_candidates"][0]["event_trigger_match"]["checks"]["recorded_trigger_keywords"] + assert catalog["scored_candidates"][0]["outcome_validation"]["status"] == "local_outcome_recorded_trigger_not_replayed" + assert "D9" in catalog["scored_candidates"][0]["similarities"]["evidence_section_overlap"] diff --git a/tests/test_vedastro_external_technique_evidence.py b/tests/test_vedastro_external_technique_evidence.py index 92e91aa3..7e98d0d3 100644 --- a/tests/test_vedastro_external_technique_evidence.py +++ b/tests/test_vedastro_external_technique_evidence.py @@ -442,6 +442,15 @@ def test_strict_workflow_uses_shared_consultation_executor(monkeypatch) -> None: "rectification": {"success": True, "endpoint": "rectification_gate"}, "thematic_report": {"success": True, "endpoint": "thematic_report"}, "vedastro_official": {"available": True}, + "runtime_truth": { + "catalog_boundary": "catalog_recognized_not_full_runtime_execution", + "official_execution_layers": {"chart_core": "ok", "event_radar": "partial"}, + }, + "interpretation_source_runtime_coverage": { + "source_pack_status": "used", + "proven_runtime_markers": ["dasha_timing_layer_used"], + "runtime_visibility_status": "partial", + }, } seen = {} @@ -468,5 +477,8 @@ def test_strict_workflow_uses_shared_consultation_executor(monkeypatch) -> None: assert seen["entry_mode"] == "direct_chart" assert seen["question"] == "我的财务今年如何?" + assert result["runtime_truth"]["official_execution_layers"]["chart_core"] == "ok" + assert result["interpretation_source_runtime_coverage"]["source_pack_status"] == "used" + assert result["interpretation_source_runtime_coverage"]["runtime_visibility_status"] == "partial" assert result["runtime_planner"]["surface"] == "skill_mcp" assert result["routing"]["question_type"] == "finance" diff --git a/tests/test_vedastro_official_full_snapshot.py b/tests/test_vedastro_official_full_snapshot.py index 59ff701c..4030dc87 100644 --- a/tests/test_vedastro_official_full_snapshot.py +++ b/tests/test_vedastro_official_full_snapshot.py @@ -7,10 +7,18 @@ import sys import types from pathlib import Path +import pytest + ROOT = Path(__file__).resolve().parents[1] +@pytest.fixture(autouse=True) +def _disable_official_full_snapshot_semantic_cache(monkeypatch): + monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0") + + + def _run_adapter(*args: str, env: dict[str, str] | None = None) -> dict: completed = subprocess.run( [sys.executable, "scripts/vedastro_service_adapter.py", *args], @@ -187,6 +195,7 @@ def test_official_full_snapshot_marks_semantic_rate_limit_payloads(monkeypatch) monkeypatch.setenv("VEDASTRO_API_ENDPOINT", "https://example.invalid/api") monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "1") + monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0") monkeypatch.setattr(adapter, "_post_official_snapshot_section", fake_post) result = adapter.run_official_full_snapshot_for_case( @@ -207,6 +216,10 @@ def test_official_full_snapshot_marks_semantic_rate_limit_payloads(monkeypatch) assert ("chart_core", "Mars") in calls assert result["section_statuses"]["chart_core"] == "rate_limited" assert result["section_statuses"]["chart_core_fanout"]["Mars"] == "rate_limited" + assert result["raw_response"]["source"] == "vedastro_official_full_snapshot" + assert result["raw_response"]["sections"]["chart_core"]["Mars"]["Status"] == "Fail" + assert result["raw_response"]["section_statuses"]["chart_core"] == "rate_limited" + assert result["raw_response"]["request_manifest"]["source_role"] == "primary_official_raw_evidence" assert result["source_metadata"]["rate_limited_sections"] == ["chart_core"] assert result["source_metadata"]["production_hint"] == "configure_vedastro_api_key_or_self_host_official_api" @@ -547,6 +560,7 @@ def test_official_full_snapshot_can_use_python_bridge_bundle_without_rest_endpoi monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False) monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False) + monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0") monkeypatch.setattr( adapter, "_try_official_capability_runner_snapshot_bundle", @@ -652,6 +666,8 @@ def test_official_full_snapshot_can_use_python_bridge_bundle_without_rest_endpoi def test_official_full_snapshot_prefers_official_capability_runner_bundle(monkeypatch) -> None: from scripts import vedastro_service_adapter as adapter + monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0") + monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False) monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False) @@ -743,6 +759,8 @@ def test_official_full_snapshot_prefers_official_capability_runner_bundle(monkey def test_official_full_snapshot_attaches_full_capability_catalog_summary(monkeypatch) -> None: from scripts import vedastro_service_adapter as adapter + monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0") + monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False) monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False) @@ -859,6 +877,8 @@ def test_official_full_snapshot_attaches_full_capability_catalog_summary(monkeyp def test_official_full_snapshot_marks_ok_when_fast_primary_sections_are_filled(monkeypatch) -> None: from scripts import vedastro_service_adapter as adapter + monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0") + monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False) monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False) monkeypatch.setattr( @@ -936,6 +956,8 @@ def test_official_full_snapshot_marks_ok_when_fast_primary_sections_are_filled(m def test_official_full_snapshot_skips_rest_sections_already_filled_by_python_bundle(monkeypatch) -> None: from scripts import vedastro_service_adapter as adapter + monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0") + rest_calls: list[tuple[str, object]] = [] monkeypatch.setattr( adapter, diff --git a/tests/test_vedastro_official_mcp_bridge.py b/tests/test_vedastro_official_mcp_bridge.py index afe96c08..30ecc06c 100644 --- a/tests/test_vedastro_official_mcp_bridge.py +++ b/tests/test_vedastro_official_mcp_bridge.py @@ -27,6 +27,7 @@ def test_vedastro_official_mcp_bridge_schema_is_declared() -> None: assert report["role"] == "official_public_mcp_thin_bridge" assert "initialize" in report["operations"] assert "tools_list" in report["operations"] + assert "call_tool" in report["operations"] def test_vedastro_official_mcp_bridge_can_list_tools_against_mock_server() -> None: @@ -99,11 +100,79 @@ def test_vedastro_official_mcp_bridge_can_list_tools_against_mock_server() -> No server.shutdown() thread.join(timeout=5) + +def test_vedastro_official_mcp_bridge_can_call_tool_against_mock_server() -> None: + seen_arguments: dict[str, object] = {} + + class Handler(BaseHTTPRequestHandler): + def do_POST(self) -> None: # noqa: N802 + length = int(self.headers.get("Content-Length", "0")) + payload = json.loads(self.rfile.read(length).decode("utf-8")) + if payload["method"] == "initialize": + response = { + "jsonrpc": "2.0", + "id": payload["id"], + "result": {"protocolVersion": "2025-06-18", "capabilities": {}}, + } + body = json.dumps(response).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Mcp-Session-Id", "session-demo") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + assert payload["method"] == "tools/call" + assert self.headers.get("Mcp-Session-Id") == "session-demo" + assert payload["params"]["name"] == "get_dasa_at_time" + seen_arguments.update(payload["params"]["arguments"]) + response = { + "jsonrpc": "2.0", + "id": payload["id"], + "result": {"content": [{"type": "text", "text": "Saturn/Venus"}]}, + } + body = json.dumps(response).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, format: str, *args) -> None: # noqa: A003 + return + + server = HTTPServer(("127.0.0.1", 0), Handler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + endpoint = f"http://127.0.0.1:{server.server_port}/api/mcp/public" + completed = subprocess.run( + [ + sys.executable, + "scripts/vedastro_official_mcp_bridge.py", + "--endpoint", + endpoint, + "--operation", + "call_tool", + "--tool", + "get_dasa_at_time", + "--arguments-json", + '{"birth_date":"17/04/REDACTED_YEAR","check_date":"04/07/2026"}', + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=120, + check=False, + ) + finally: + server.shutdown() + thread.join(timeout=5) + assert completed.returncode == 0, completed.stderr or completed.stdout report = json.loads(completed.stdout) - assert report["available"] is True - assert report["status"] == "ok" - assert report["operation"] == "tools_list" - assert report["session_id"] == "session-demo" - assert report["tool_count"] == 2 - assert report["tool_names"] == ["get_current_transits", "get_dasa_at_time"] + assert report["operation"] == "call_tool" + assert report["tool_name"] == "get_dasa_at_time" + assert report["result"]["content"][0]["text"] == "Saturn/Venus" + assert seen_arguments["birth_date"] == "17/04/REDACTED_YEAR" diff --git a/tests/test_vedastro_runtime_mode_diagnostics.py b/tests/test_vedastro_runtime_mode_diagnostics.py index 85bb2d14..514a24ab 100644 --- a/tests/test_vedastro_runtime_mode_diagnostics.py +++ b/tests/test_vedastro_runtime_mode_diagnostics.py @@ -35,7 +35,12 @@ def test_vedastro_diagnostics_reports_fast_fallback_mode_without_endpoint() -> N assert report["mode"] == "fast_local_fallback" assert report["official_ready"] is False assert "VEDASTRO_API_ENDPOINT" in report["missing"] + assert "missing_endpoint" in report["readiness_blockers"] + assert "network_disabled" in report["readiness_blockers"] + assert "timeout_too_low" in report["readiness_blockers"] assert report["expected_fallback_status"] == "official_snapshot_budget_exhausted_or_endpoint_blocked" + assert "VEDASTRO_API_ENDPOINT" in report["official_closure_plan"]["required_env"] + assert report["official_closure_plan"]["raw_response_acceptance"] == "vedastro_official.raw_response must be present before claiming official cloud closure." def test_vedastro_diagnostics_reports_official_mode_when_configured() -> None: @@ -51,4 +56,21 @@ def test_vedastro_diagnostics_reports_official_mode_when_configured() -> None: assert report["timeout_seconds"] == 20.0 assert report["network_enabled"] is True assert report["has_api_key"] is True + assert report["readiness_blockers"] == [] + assert report["official_closure_plan"]["required_env"]["VEDASTRO_TIMEOUT_SECONDS"] == "20" + +def test_vedastro_diagnostics_labels_premium_key_missing_but_free_tier_possible() -> None: + report = _run_diag({ + "VEDASTRO_API_ENDPOINT": "https://api.vedastro.org/api", + "VEDASTRO_ENABLE_NETWORK": "1", + "VEDASTRO_TIMEOUT_SECONDS": "20", + "VEDASTRO_API_KEY": "", + "VEDASTRO_FREE_TIER_QUEUE": "1", + }) + assert report["mode"] == "official_extended" + assert report["official_ready"] is True + assert "missing_api_key" not in report["readiness_blockers"] + assert "premium_key_missing" in report["readiness_blockers"] + assert report["free_tier_possible_with_cache_queue"] is True + assert report["official_closure_plan"]["premium_key_policy"] == "API key recommended for stable official full snapshot; free tier may still block or throttle." diff --git a/tests/test_vedastro_user_entrypoint.py b/tests/test_vedastro_user_entrypoint.py index 5555e566..5aaa8a2e 100644 --- a/tests/test_vedastro_user_entrypoint.py +++ b/tests/test_vedastro_user_entrypoint.py @@ -120,6 +120,11 @@ def test_user_entrypoint_runs_catalog_and_strict_workflow_contract() -> None: assert report["cache_and_queue"]["official_full_snapshot_cache_ttl_seconds"] == 600 assert report["cache_and_queue"]["range_scan_cache_ttl_seconds"] == 600 assert report["cache_and_queue"]["free_tier_queue_enabled"] is True + assert report["runtime_mode"]["free_tier_possible_with_cache_queue"] is True + assert report["runtime_mode"]["readiness_blockers"] == ["premium_key_missing"] + assert report["cache_and_queue"]["free_tier_strategy"]["using_free_tier"] is True + assert report["cache_and_queue"]["free_tier_strategy"]["queue_enabled"] is True + assert report["cache_and_queue"]["free_tier_strategy"]["guard_status"] == "within_free_tier_strategy" assert report["strict_workflow"]["triggered"] is True assert report["strict_workflow"]["primary_route"] == "career" assert "career" in report["strict_workflow"]["routes_available"] diff --git a/tests/test_vibe_coding_setup.py b/tests/test_vibe_coding_setup.py new file mode 100644 index 00000000..0321a639 --- /dev/null +++ b/tests/test_vibe_coding_setup.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_print_cline_mcp_config_points_at_current_repo_mcp_server() -> None: + completed = subprocess.run( + [sys.executable, "scripts/print_cline_mcp_config.py"], + cwd=ROOT, + text=True, + capture_output=True, + timeout=30, + check=False, + ) + + assert completed.returncode == 0, completed.stderr or completed.stdout + config = json.loads(completed.stdout) + server = config["mcpServers"]["jyotish"] + assert server["command"] == sys.executable + assert server["args"] == [str(ROOT / "mcp_server.py")] + assert server["cwd"] == str(ROOT) + assert server["env"]["PYTHONPATH"].endswith("/scripts") + + +def test_cline_project_config_is_ignored_and_installable(tmp_path: Path) -> None: + completed = subprocess.run( + [ + sys.executable, + str(ROOT / "scripts" / "print_cline_mcp_config.py"), + "--repo-root", + str(tmp_path), + "--install-project", + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=30, + check=False, + ) + + assert completed.returncode == 0, completed.stderr or completed.stdout + written = tmp_path / ".cline" / "mcp.json" + assert written.exists() + config = json.loads(written.read_text(encoding="utf-8")) + assert config["mcpServers"]["jyotish"]["args"] == [str(tmp_path / "mcp_server.py")] + assert ".cline/" in (ROOT / ".gitignore").read_text(encoding="utf-8") +