@researai/deepscientist 1.5.14 → 1.5.15
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 +8 -0
- package/assets/branding/logo-raster.png +0 -0
- package/bin/ds.js +134 -49
- package/docs/en/00_QUICK_START.md +2 -2
- package/docs/en/01_SETTINGS_REFERENCE.md +20 -4
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +2 -0
- 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/README.md +6 -0
- package/docs/zh/00_QUICK_START.md +2 -2
- package/docs/zh/01_SETTINGS_REFERENCE.md +20 -4
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +2 -0
- 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/README.md +6 -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/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 +3534 -191
- 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 +79 -64
- package/src/deepscientist/bash_exec/shells.py +87 -0
- package/src/deepscientist/bridges/connectors.py +51 -2
- package/src/deepscientist/config/models.py +6 -3
- package/src/deepscientist/config/service.py +7 -2
- package/src/deepscientist/connector/weixin_support.py +122 -1
- package/src/deepscientist/daemon/api/handlers.py +75 -4
- package/src/deepscientist/daemon/api/router.py +1 -0
- package/src/deepscientist/daemon/app.py +758 -206
- package/src/deepscientist/doctor.py +51 -0
- package/src/deepscientist/file_lock.py +48 -0
- package/src/deepscientist/gitops/diff.py +167 -1
- package/src/deepscientist/mcp/server.py +173 -5
- package/src/deepscientist/process_control.py +161 -0
- package/src/deepscientist/prompts/builder.py +267 -442
- package/src/deepscientist/quest/service.py +2255 -163
- package/src/deepscientist/quest/stage_views.py +171 -0
- package/src/deepscientist/runners/base.py +2 -0
- package/src/deepscientist/runners/codex.py +88 -5
- package/src/deepscientist/runners/runtime_overrides.py +17 -1
- package/src/prompts/contracts/shared_interaction.md +13 -4
- package/src/prompts/system.md +916 -72
- package/src/skills/analysis-campaign/SKILL.md +31 -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 +2 -0
- package/src/skills/decision/SKILL.md +19 -2
- package/src/skills/experiment/SKILL.md +8 -2
- package/src/skills/finalize/SKILL.md +18 -0
- package/src/skills/idea/SKILL.md +78 -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 +1 -1
- package/src/skills/optimize/SKILL.md +1644 -0
- package/src/skills/rebuttal/SKILL.md +2 -1
- package/src/skills/review/SKILL.md +2 -1
- package/src/skills/write/SKILL.md +80 -12
- package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
- package/src/tui/dist/app/AppContainer.js +3 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-DaF9Nge_.js → AiManusChatView-DDjbFnbt.js} +12 -12
- package/src/ui/dist/assets/{AnalysisPlugin-BSVx6dXE.js → AnalysisPlugin-Yb5IdmaU.js} +1 -1
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +31037 -0
- package/src/ui/dist/assets/{CodeEditorPlugin-DU9G0Tox.js → CodeEditorPlugin-C4D2TIkU.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-DoX_fI9l.js → CodeViewerPlugin-BVoNZIvC.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-C4FWIXuU.js → DocViewerPlugin-CLChbllo.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-BgfFMgtf.js → GitDiffViewerPlugin-C4xeFyFQ.js} +20 -20
- package/src/ui/dist/assets/{ImageViewerPlugin-tcPkfY_x.js → ImageViewerPlugin-OiMUAcLi.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-_dKV60Bf.js → LabCopilotPanel-BjD2ThQF.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-Bje0ayoC.js → LabPlugin-DQPg-NrB.js} +2 -2
- package/src/ui/dist/assets/{LatexPlugin-CVsBzAln.js → LatexPlugin-CI05XAV9.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-xjmrqv_8.js → MarkdownViewerPlugin-DpeBLYZf.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-mMM2A8wP.js → MarketplacePlugin-DolE58Q2.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-3kVDSOBo.js → NotebookEditor-7Qm2rSWD.js} +11 -11
- package/src/ui/dist/assets/{NotebookEditor-SoJ8X-MO.js → NotebookEditor-C1kWaxKi.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-DElVuHl9.js → PdfLoader-BfOHw8Zw.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-Bq88XT4G.js → PdfMarkdownPlugin-BulDREv1.js} +2 -2
- package/src/ui/dist/assets/{PdfViewerPlugin-CsCXMo9S.js → PdfViewerPlugin-C-daaOaL.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-oUPvy19k.js → SearchPlugin-CjpaiJ3A.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-CRkT9yNy.js → TextViewerPlugin-BxIyqPQC.js} +5 -5
- package/src/ui/dist/assets/{VNCViewer-BgbuvWhR.js → VNCViewer-HAg9mF7M.js} +10 -10
- package/src/ui/dist/assets/{bot-v_RASACv.js → bot-0DYntytV.js} +1 -1
- package/src/ui/dist/assets/{code-5hC9d0VH.js → code-B20Slj_w.js} +1 -1
- package/src/ui/dist/assets/{file-content-D1PxfOrp.js → file-content-DT24KFma.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-DG1oT_Hj.js → file-diff-panel-DK13YPql.js} +1 -1
- package/src/ui/dist/assets/{file-socket-BmdFYQlk.js → file-socket-B4T2o4nR.js} +1 -1
- package/src/ui/dist/assets/{image-Dqe2X2tW.js → image-DSeR_sDS.js} +1 -1
- package/src/ui/dist/assets/{index-RDlNXXx1.js → index-BrFje2Uk.js} +2 -2
- package/src/ui/dist/assets/{index-DVsMKK_y.js → index-BwRJaoTl.js} +1 -1
- package/src/ui/dist/assets/{index-Nt9hS4ck.js → index-D_E4281X.js} +5007 -28514
- package/src/ui/dist/assets/{index-Duvz8Ip0.js → index-DnYB3xb1.js} +12 -12
- package/src/ui/dist/assets/{index-BQG-1s2o.css → index-G7AcWcMu.css} +43 -2
- package/src/ui/dist/assets/{monaco-DIXge1CP.js → monaco-LExaAN3Y.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-BBTTQaO-.js → pdf-effect-queue-BJk5okWJ.js} +1 -1
- package/src/ui/dist/assets/{popover-BWlolyxo.js → popover-D3Gg_FoV.js} +1 -1
- package/src/ui/dist/assets/{project-sync-BM5PkFH4.js → project-sync-C_ygLlVU.js} +1 -1
- package/src/ui/dist/assets/{select-D4dAtrA8.js → select-CpAK6uWm.js} +2 -2
- package/src/ui/dist/assets/{sigma-CKbE5jJT.js → sigma-DEccaSgk.js} +1 -1
- package/src/ui/dist/assets/{square-check-big-CZNGMgiB.js → square-check-big-uUfyVsbD.js} +1 -1
- package/src/ui/dist/assets/{trash-DaB37xAz.js → trash-CXvwwSe8.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-C2OmAcWe.js → useCliAccess-Bnop4mgR.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-Dowd1Ij4.js → useFileDiffOverlay-B8eUAX0I.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-BGjAhAUq.js → wrap-text-9vbOBpkW.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-dMZQMXzc.js → zoom-out-BgVMmOW4.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/CliPlugin-C9gzJX41.js +0 -5905
|
@@ -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":
|