@researai/deepscientist 1.5.14 → 1.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +336 -90
- package/assets/branding/logo-raster.png +0 -0
- package/bin/ds.js +816 -131
- package/docs/en/00_QUICK_START.md +36 -15
- package/docs/en/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +11 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/en/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/en/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/en/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/README.md +24 -0
- package/docs/zh/00_QUICK_START.md +36 -15
- package/docs/zh/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +11 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/zh/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/zh/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/zh/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/README.md +24 -0
- package/install.sh +2 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/charts.py +567 -0
- package/src/deepscientist/artifact/guidance.py +50 -10
- package/src/deepscientist/artifact/metrics.py +228 -5
- package/src/deepscientist/artifact/schemas.py +3 -0
- package/src/deepscientist/artifact/service.py +4276 -308
- package/src/deepscientist/bash_exec/models.py +23 -0
- package/src/deepscientist/bash_exec/monitor.py +147 -67
- package/src/deepscientist/bash_exec/runtime.py +218 -156
- package/src/deepscientist/bash_exec/service.py +309 -69
- package/src/deepscientist/bash_exec/shells.py +87 -0
- package/src/deepscientist/bridges/connectors.py +51 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +232 -0
- package/src/deepscientist/config/models.py +8 -4
- package/src/deepscientist/config/service.py +38 -11
- package/src/deepscientist/connector/weixin_support.py +122 -1
- package/src/deepscientist/daemon/api/handlers.py +199 -9
- package/src/deepscientist/daemon/api/router.py +5 -0
- package/src/deepscientist/daemon/app.py +1458 -289
- package/src/deepscientist/doctor.py +51 -0
- package/src/deepscientist/file_lock.py +48 -0
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +296 -1
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +212 -5
- package/src/deepscientist/process_control.py +161 -0
- package/src/deepscientist/prompts/builder.py +501 -453
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +2539 -195
- package/src/deepscientist/quest/stage_views.py +177 -1
- package/src/deepscientist/runners/base.py +2 -0
- package/src/deepscientist/runners/codex.py +169 -31
- package/src/deepscientist/runners/runtime_overrides.py +17 -1
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +24 -4
- package/src/prompts/system.md +921 -72
- package/src/prompts/system_copilot.md +43 -0
- package/src/skills/analysis-campaign/SKILL.md +32 -2
- package/src/skills/analysis-campaign/references/artifact-orchestration.md +1 -1
- package/src/skills/analysis-campaign/references/writing-facing-slice-examples.md +65 -0
- package/src/skills/baseline/SKILL.md +10 -0
- package/src/skills/decision/SKILL.md +27 -2
- package/src/skills/experiment/SKILL.md +16 -2
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +19 -0
- package/src/skills/idea/SKILL.md +79 -0
- package/src/skills/idea/references/idea-generation-playbook.md +100 -0
- package/src/skills/idea/references/outline-seeding-example.md +60 -0
- package/src/skills/intake-audit/SKILL.md +9 -1
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1645 -0
- package/src/skills/rebuttal/SKILL.md +3 -1
- package/src/skills/review/SKILL.md +3 -1
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +81 -12
- package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
- package/src/tui/dist/app/AppContainer.js +22 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-DnSm0GZn.js +1 -0
- package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-itb0tltR.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DqKkiCI6.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-DxL2ezFG.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-B_RQm1YZ.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-tHqlXY3n.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +14 -0
- package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-B495DTXC.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DG28-61B.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BiOGT-Kj.js +13 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +81 -0
- package/src/ui/dist/assets/NotebookEditor-CVsj8h_T.js +361 -0
- package/src/ui/dist/assets/PdfLoader-CASDQmxJ.js +16 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-BFhwoKsY.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-CB4DYfWO.js +54 -0
- package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +11 -0
- package/src/ui/dist/assets/bot-CFkZY-JP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +6 -0
- package/src/ui/dist/assets/code-DLC6G24T.js +6 -0
- package/src/ui/dist/assets/file-content-Dv4LoZec.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-Cu4Qln7Y.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +6 -0
- package/src/ui/dist/assets/image-B9HUUddG.js +6 -0
- package/src/ui/dist/assets/index-B2B1sg-M.js +1 -0
- package/src/ui/dist/assets/index-Cgla8biy.css +33 -0
- package/src/ui/dist/assets/index-DRyx7vAc.js +1 -0
- package/src/ui/dist/assets/index-Gbl53BNp.js +2496 -0
- package/src/ui/dist/assets/index-wQ7RIIRd.js +11 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-DL6h35vr.js +1 -0
- package/src/ui/dist/assets/project-sync-CsX08Qno.js +1 -0
- package/src/ui/dist/assets/select-DvmXt1yY.js +11 -0
- package/src/ui/dist/assets/sigma-7jpXazui.js +6 -0
- package/src/ui/dist/assets/trash-xA7kFt8i.js +11 -0
- package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-CwMn-iqb.js +11 -0
- package/src/ui/dist/assets/zoom-out-R-GWEhzS.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DaF9Nge_.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-BSVx6dXE.js +0 -123
- package/src/ui/dist/assets/CliPlugin-C9gzJX41.js +0 -5905
- package/src/ui/dist/assets/CodeEditorPlugin-DU9G0Tox.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-DoX_fI9l.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-C4FWIXuU.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-BgfFMgtf.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-tcPkfY_x.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-_dKV60Bf.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-Bje0ayoC.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CVsBzAln.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-xjmrqv_8.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-mMM2A8wP.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-3kVDSOBo.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/NotebookEditor-SoJ8X-MO.js +0 -84873
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfLoader-DElVuHl9.js +0 -25468
- package/src/ui/dist/assets/PdfMarkdownPlugin-Bq88XT4G.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-CsCXMo9S.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/SearchPlugin-oUPvy19k.js +0 -741
- package/src/ui/dist/assets/TextViewerPlugin-CRkT9yNy.js +0 -472
- package/src/ui/dist/assets/VNCViewer-BgbuvWhR.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-v_RASACv.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-5hC9d0VH.js +0 -17
- package/src/ui/dist/assets/file-content-D1PxfOrp.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DG1oT_Hj.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-BmdFYQlk.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-Dqe2X2tW.js +0 -18
- package/src/ui/dist/assets/index-BQG-1s2o.css +0 -12553
- package/src/ui/dist/assets/index-DVsMKK_y.js +0 -25
- package/src/ui/dist/assets/index-Duvz8Ip0.js +0 -159
- package/src/ui/dist/assets/index-Nt9hS4ck.js +0 -244829
- package/src/ui/dist/assets/index-RDlNXXx1.js +0 -120
- package/src/ui/dist/assets/monaco-DIXge1CP.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BBTTQaO-.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-BWlolyxo.js +0 -476
- package/src/ui/dist/assets/project-sync-BM5PkFH4.js +0 -297
- package/src/ui/dist/assets/select-D4dAtrA8.js +0 -1690
- package/src/ui/dist/assets/sigma-CKbE5jJT.js +0 -22
- package/src/ui/dist/assets/square-check-big-CZNGMgiB.js +0 -17
- package/src/ui/dist/assets/trash-DaB37xAz.js +0 -32
- package/src/ui/dist/assets/useCliAccess-C2OmAcWe.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-Dowd1Ij4.js +0 -53
- package/src/ui/dist/assets/wrap-text-BGjAhAUq.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-dMZQMXzc.js +0 -34
|
@@ -581,6 +581,18 @@ def normalize_metric_contract(
|
|
|
581
581
|
def selected_baseline_metrics(entry: dict[str, Any] | None, selected_variant_id: str | None = None) -> dict[str, Any]:
|
|
582
582
|
if not isinstance(entry, dict) or not entry:
|
|
583
583
|
return {}
|
|
584
|
+
|
|
585
|
+
def with_primary_metric(summary: dict[str, float], primary_metric: object) -> dict[str, float]:
|
|
586
|
+
resolved = OrderedDict(summary)
|
|
587
|
+
if isinstance(primary_metric, dict):
|
|
588
|
+
metric_id = str(
|
|
589
|
+
primary_metric.get("metric_id") or primary_metric.get("name") or primary_metric.get("id") or ""
|
|
590
|
+
).strip()
|
|
591
|
+
value = to_number(primary_metric.get("value"))
|
|
592
|
+
if metric_id and value is not None and metric_id not in resolved:
|
|
593
|
+
resolved[metric_id] = value
|
|
594
|
+
return dict(resolved)
|
|
595
|
+
|
|
584
596
|
variants = entry.get("baseline_variants") if isinstance(entry.get("baseline_variants"), list) else []
|
|
585
597
|
target_id = str(selected_variant_id or entry.get("default_variant_id") or "").strip()
|
|
586
598
|
selected_variant = None
|
|
@@ -592,15 +604,33 @@ def selected_baseline_metrics(entry: dict[str, Any] | None, selected_variant_id:
|
|
|
592
604
|
if selected_variant is None and variants:
|
|
593
605
|
selected_variant = next((item for item in variants if isinstance(item, dict)), None)
|
|
594
606
|
if isinstance(selected_variant, dict):
|
|
595
|
-
summary =
|
|
607
|
+
summary = with_primary_metric(
|
|
608
|
+
extract_numeric_metric_map(metrics_summary=selected_variant.get("metrics_summary")),
|
|
609
|
+
selected_variant.get("primary_metric"),
|
|
610
|
+
)
|
|
596
611
|
if summary:
|
|
597
612
|
return summary
|
|
598
|
-
return
|
|
613
|
+
return with_primary_metric(
|
|
614
|
+
extract_numeric_metric_map(metrics_summary=entry.get("metrics_summary")),
|
|
615
|
+
entry.get("primary_metric"),
|
|
616
|
+
)
|
|
599
617
|
|
|
600
618
|
|
|
601
619
|
def baseline_metric_lines(entry: dict[str, Any] | None, selected_variant_id: str | None = None) -> list[dict[str, Any]]:
|
|
602
620
|
if not isinstance(entry, dict) or not entry:
|
|
603
621
|
return []
|
|
622
|
+
|
|
623
|
+
def metrics_with_primary(summary: object, primary_metric: object) -> dict[str, float]:
|
|
624
|
+
resolved = OrderedDict(extract_numeric_metric_map(metrics_summary=summary))
|
|
625
|
+
if isinstance(primary_metric, dict):
|
|
626
|
+
metric_id = str(
|
|
627
|
+
primary_metric.get("metric_id") or primary_metric.get("name") or primary_metric.get("id") or ""
|
|
628
|
+
).strip()
|
|
629
|
+
value = to_number(primary_metric.get("value"))
|
|
630
|
+
if metric_id and value is not None and metric_id not in resolved:
|
|
631
|
+
resolved[metric_id] = value
|
|
632
|
+
return dict(resolved)
|
|
633
|
+
|
|
604
634
|
baseline_id = str(entry.get("baseline_id") or entry.get("entry_id") or "").strip() or None
|
|
605
635
|
selected_id = str(selected_variant_id or entry.get("default_variant_id") or "").strip() or None
|
|
606
636
|
lines: list[dict[str, Any]] = []
|
|
@@ -609,12 +639,13 @@ def baseline_metric_lines(entry: dict[str, Any] | None, selected_variant_id: str
|
|
|
609
639
|
if not isinstance(variant, dict):
|
|
610
640
|
continue
|
|
611
641
|
variant_id = str(variant.get("variant_id") or "").strip() or None
|
|
612
|
-
|
|
642
|
+
variant_label = str(variant.get("label") or variant_id or "variant").strip() or "variant"
|
|
643
|
+
metrics_summary = metrics_with_primary(variant.get("metrics_summary"), variant.get("primary_metric"))
|
|
613
644
|
for metric_id, value in metrics_summary.items():
|
|
614
645
|
lines.append(
|
|
615
646
|
{
|
|
616
647
|
"metric_id": metric_id,
|
|
617
|
-
"label": f"{baseline_id or 'baseline'}:{
|
|
648
|
+
"label": f"{baseline_id or 'baseline'}:{variant_label}",
|
|
618
649
|
"baseline_id": baseline_id,
|
|
619
650
|
"variant_id": variant_id,
|
|
620
651
|
"selected": bool(selected_id and variant_id == selected_id),
|
|
@@ -624,7 +655,7 @@ def baseline_metric_lines(entry: dict[str, Any] | None, selected_variant_id: str
|
|
|
624
655
|
)
|
|
625
656
|
if lines:
|
|
626
657
|
return lines
|
|
627
|
-
for metric_id, value in
|
|
658
|
+
for metric_id, value in metrics_with_primary(entry.get("metrics_summary"), entry.get("primary_metric")).items():
|
|
628
659
|
lines.append(
|
|
629
660
|
{
|
|
630
661
|
"metric_id": metric_id,
|
|
@@ -639,6 +670,198 @@ def baseline_metric_lines(entry: dict[str, Any] | None, selected_variant_id: str
|
|
|
639
670
|
return lines
|
|
640
671
|
|
|
641
672
|
|
|
673
|
+
def build_baseline_compare_payload(
|
|
674
|
+
*,
|
|
675
|
+
quest_id: str,
|
|
676
|
+
baseline_entries: list[dict[str, Any]],
|
|
677
|
+
active_baseline_id: str | None = None,
|
|
678
|
+
active_variant_id: str | None = None,
|
|
679
|
+
) -> dict[str, Any]:
|
|
680
|
+
series_map: OrderedDict[str, dict[str, Any]] = OrderedDict()
|
|
681
|
+
baseline_meta_map: dict[str, dict[str, Any]] = {}
|
|
682
|
+
deduped_entries: OrderedDict[str, dict[str, Any]] = OrderedDict()
|
|
683
|
+
ordered_baseline_entries: list[dict[str, Any]] = []
|
|
684
|
+
primary_metric_id: str | None = None
|
|
685
|
+
active_baseline_text = str(active_baseline_id or "").strip() or None
|
|
686
|
+
active_variant_text = str(active_variant_id or "").strip() or None
|
|
687
|
+
|
|
688
|
+
def entry_variant_groups(entry: dict[str, Any]) -> list[tuple[str | None, dict[str, Any] | None]]:
|
|
689
|
+
variants = entry.get("baseline_variants") if isinstance(entry.get("baseline_variants"), list) else []
|
|
690
|
+
if variants:
|
|
691
|
+
groups: list[tuple[str | None, dict[str, Any] | None]] = []
|
|
692
|
+
for variant in variants:
|
|
693
|
+
if not isinstance(variant, dict):
|
|
694
|
+
continue
|
|
695
|
+
groups.append((str(variant.get("variant_id") or "").strip() or None, variant))
|
|
696
|
+
if groups:
|
|
697
|
+
return groups
|
|
698
|
+
return [(None, None)]
|
|
699
|
+
|
|
700
|
+
def entry_key(entry: dict[str, Any], *, variant_id: str | None) -> str:
|
|
701
|
+
baseline_id = str(entry.get("baseline_id") or entry.get("entry_id") or "").strip() or "baseline"
|
|
702
|
+
variant_text = (
|
|
703
|
+
str(variant_id or entry.get("default_variant_id") or "").strip()
|
|
704
|
+
or "default"
|
|
705
|
+
)
|
|
706
|
+
return f"{baseline_id}::{variant_text}"
|
|
707
|
+
|
|
708
|
+
def is_selected(entry: dict[str, Any], *, variant_id: str | None) -> bool:
|
|
709
|
+
baseline_id = str(entry.get("baseline_id") or entry.get("entry_id") or "").strip() or None
|
|
710
|
+
if not baseline_id or baseline_id != active_baseline_text:
|
|
711
|
+
return False
|
|
712
|
+
resolved_variant_id = str(variant_id or entry.get("default_variant_id") or "").strip() or None
|
|
713
|
+
if active_variant_text:
|
|
714
|
+
return resolved_variant_id == active_variant_text
|
|
715
|
+
if resolved_variant_id:
|
|
716
|
+
default_variant_id = str(entry.get("default_variant_id") or "").strip() or None
|
|
717
|
+
return resolved_variant_id == (default_variant_id or resolved_variant_id)
|
|
718
|
+
return True
|
|
719
|
+
|
|
720
|
+
def ensure_series(metric_id: str, meta: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
721
|
+
resolved_meta = meta or baseline_meta_map.get(metric_id) or _normalize_metric_entry({}, fallback_id=metric_id)
|
|
722
|
+
if metric_id not in series_map:
|
|
723
|
+
series_map[metric_id] = {
|
|
724
|
+
"metric_id": metric_id,
|
|
725
|
+
"label": resolved_meta.get("label") or metric_id,
|
|
726
|
+
"direction": normalize_metric_direction(resolved_meta.get("direction"), metric_id=metric_id),
|
|
727
|
+
"unit": resolved_meta.get("unit"),
|
|
728
|
+
"decimals": resolved_meta.get("decimals"),
|
|
729
|
+
"chart_group": resolved_meta.get("chart_group"),
|
|
730
|
+
"values": [],
|
|
731
|
+
}
|
|
732
|
+
else:
|
|
733
|
+
series_map[metric_id]["label"] = resolved_meta.get("label") or series_map[metric_id]["label"]
|
|
734
|
+
series_map[metric_id]["direction"] = normalize_metric_direction(
|
|
735
|
+
resolved_meta.get("direction") or series_map[metric_id]["direction"],
|
|
736
|
+
metric_id=metric_id,
|
|
737
|
+
)
|
|
738
|
+
series_map[metric_id]["unit"] = resolved_meta.get("unit") or series_map[metric_id]["unit"]
|
|
739
|
+
if resolved_meta.get("decimals") is not None:
|
|
740
|
+
series_map[metric_id]["decimals"] = resolved_meta.get("decimals")
|
|
741
|
+
series_map[metric_id]["chart_group"] = (
|
|
742
|
+
resolved_meta.get("chart_group") or series_map[metric_id]["chart_group"]
|
|
743
|
+
)
|
|
744
|
+
return series_map[metric_id]
|
|
745
|
+
|
|
746
|
+
for entry in baseline_entries:
|
|
747
|
+
if not isinstance(entry, dict):
|
|
748
|
+
continue
|
|
749
|
+
baseline_id = str(entry.get("baseline_id") or entry.get("entry_id") or "").strip() or None
|
|
750
|
+
if not baseline_id:
|
|
751
|
+
continue
|
|
752
|
+
for variant_id, _variant in entry_variant_groups(entry):
|
|
753
|
+
deduped_entries[entry_key(entry, variant_id=variant_id)] = {
|
|
754
|
+
**entry,
|
|
755
|
+
"_compare_variant_id": variant_id,
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
for normalized_entry in deduped_entries.values():
|
|
759
|
+
variant_id = str(normalized_entry.get("_compare_variant_id") or "").strip() or None
|
|
760
|
+
contract = normalize_metric_contract(
|
|
761
|
+
normalized_entry.get("metric_contract"),
|
|
762
|
+
baseline_id=str(normalized_entry.get("baseline_id") or normalized_entry.get("entry_id") or ""),
|
|
763
|
+
metrics_summary=selected_baseline_metrics(normalized_entry, variant_id),
|
|
764
|
+
primary_metric=normalized_entry.get("primary_metric"),
|
|
765
|
+
baseline_variants=normalized_entry.get("baseline_variants"),
|
|
766
|
+
)
|
|
767
|
+
if primary_metric_id is None:
|
|
768
|
+
candidate_primary = str(contract.get("primary_metric_id") or "").strip() or None
|
|
769
|
+
if candidate_primary:
|
|
770
|
+
primary_metric_id = candidate_primary
|
|
771
|
+
metric_meta = extract_metric_meta_map(
|
|
772
|
+
metric_contract=normalized_entry.get("metric_contract"),
|
|
773
|
+
metrics_summary=selected_baseline_metrics(normalized_entry, variant_id),
|
|
774
|
+
)
|
|
775
|
+
baseline_meta_map.update(metric_meta)
|
|
776
|
+
compare_key = entry_key(normalized_entry, variant_id=variant_id)
|
|
777
|
+
selected = is_selected(normalized_entry, variant_id=variant_id)
|
|
778
|
+
ordered_baseline_entries.append(
|
|
779
|
+
{
|
|
780
|
+
"entry_key": compare_key,
|
|
781
|
+
"baseline_id": str(normalized_entry.get("baseline_id") or normalized_entry.get("entry_id") or "").strip() or None,
|
|
782
|
+
"variant_id": variant_id,
|
|
783
|
+
"label": next(
|
|
784
|
+
(
|
|
785
|
+
str(item.get("label") or item.get("variant_id") or "").strip()
|
|
786
|
+
for item in (normalized_entry.get("baseline_variants") or [])
|
|
787
|
+
if isinstance(item, dict) and str(item.get("variant_id") or "").strip() == str(variant_id or "").strip()
|
|
788
|
+
),
|
|
789
|
+
None,
|
|
790
|
+
)
|
|
791
|
+
or (variant_id or str(normalized_entry.get("baseline_id") or "").strip() or "baseline"),
|
|
792
|
+
"baseline_kind": str(normalized_entry.get("baseline_kind") or "").strip() or None,
|
|
793
|
+
"summary": str(normalized_entry.get("summary") or "").strip() or None,
|
|
794
|
+
"selected": selected,
|
|
795
|
+
"updated_at": normalized_entry.get("updated_at") or normalized_entry.get("created_at"),
|
|
796
|
+
"metric_count": len(selected_baseline_metrics(normalized_entry, variant_id)),
|
|
797
|
+
}
|
|
798
|
+
)
|
|
799
|
+
for line in baseline_metric_lines(normalized_entry, variant_id):
|
|
800
|
+
metric_id = str(line.get("metric_id") or "").strip()
|
|
801
|
+
if not metric_id:
|
|
802
|
+
continue
|
|
803
|
+
line_variant_id = str(line.get("variant_id") or "").strip() or None
|
|
804
|
+
if line_variant_id != variant_id:
|
|
805
|
+
if not (line_variant_id is None and variant_id is None):
|
|
806
|
+
continue
|
|
807
|
+
ensure_series(metric_id, metric_meta.get(metric_id))
|
|
808
|
+
series_map[metric_id]["values"].append(
|
|
809
|
+
{
|
|
810
|
+
"entry_key": compare_key,
|
|
811
|
+
"label": line.get("label"),
|
|
812
|
+
"baseline_id": line.get("baseline_id"),
|
|
813
|
+
"variant_id": line.get("variant_id"),
|
|
814
|
+
"selected": selected,
|
|
815
|
+
"value": line.get("value"),
|
|
816
|
+
"raw_value": line.get("raw_value"),
|
|
817
|
+
"baseline_kind": str(normalized_entry.get("baseline_kind") or "").strip() or None,
|
|
818
|
+
"summary": str(normalized_entry.get("summary") or "").strip() or None,
|
|
819
|
+
"updated_at": normalized_entry.get("updated_at") or normalized_entry.get("created_at"),
|
|
820
|
+
}
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
def sort_metric_values(series: dict[str, Any]) -> None:
|
|
824
|
+
direction = normalize_metric_direction(series.get("direction"), metric_id=str(series.get("metric_id") or ""))
|
|
825
|
+
|
|
826
|
+
def sort_key(item: dict[str, Any]) -> tuple[int, float, str]:
|
|
827
|
+
value = to_number(item.get("value"))
|
|
828
|
+
if value is None:
|
|
829
|
+
metric_rank = float("inf")
|
|
830
|
+
elif direction == "minimize":
|
|
831
|
+
metric_rank = value
|
|
832
|
+
else:
|
|
833
|
+
metric_rank = -value
|
|
834
|
+
return (0 if item.get("selected") else 1, metric_rank, str(item.get("label") or ""))
|
|
835
|
+
|
|
836
|
+
series["values"].sort(key=sort_key)
|
|
837
|
+
|
|
838
|
+
for series in series_map.values():
|
|
839
|
+
sort_metric_values(series)
|
|
840
|
+
|
|
841
|
+
ordered_baseline_entries.sort(
|
|
842
|
+
key=lambda item: (
|
|
843
|
+
0 if item.get("selected") else 1,
|
|
844
|
+
str(item.get("updated_at") or ""),
|
|
845
|
+
str(item.get("baseline_id") or ""),
|
|
846
|
+
str(item.get("variant_id") or ""),
|
|
847
|
+
)
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
return {
|
|
851
|
+
"quest_id": quest_id,
|
|
852
|
+
"primary_metric_id": primary_metric_id,
|
|
853
|
+
"total_entries": len(ordered_baseline_entries),
|
|
854
|
+
"baseline_ref": {
|
|
855
|
+
"baseline_id": active_baseline_text,
|
|
856
|
+
"variant_id": active_variant_text,
|
|
857
|
+
}
|
|
858
|
+
if active_baseline_text
|
|
859
|
+
else None,
|
|
860
|
+
"entries": ordered_baseline_entries,
|
|
861
|
+
"series": [item for item in series_map.values() if item["values"]],
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
|
|
642
865
|
def normalize_metric_rows(
|
|
643
866
|
metric_rows: object,
|
|
644
867
|
*,
|
|
@@ -5,6 +5,7 @@ ARTIFACT_DIRS = {
|
|
|
5
5
|
"idea": "ideas",
|
|
6
6
|
"decision": "decisions",
|
|
7
7
|
"progress": "progress",
|
|
8
|
+
"answer": "answers",
|
|
8
9
|
"milestone": "milestones",
|
|
9
10
|
"run": "runs",
|
|
10
11
|
"report": "reports",
|
|
@@ -61,6 +62,8 @@ def guidance_for_kind(kind: str) -> str:
|
|
|
61
62
|
return "Run recorded. Compare metrics, then decide whether to continue, branch, or stop."
|
|
62
63
|
if kind == "milestone":
|
|
63
64
|
return "Milestone recorded. Send a concise progress update to the active surface."
|
|
65
|
+
if kind == "answer":
|
|
66
|
+
return "Answer stored. This was a direct user-facing reply, not a long-running progress checkpoint."
|
|
64
67
|
if kind == "report":
|
|
65
68
|
return "Report saved. Use it to update SUMMARY.md and the next planning step."
|
|
66
69
|
if kind == "approval":
|