149 lines
5.6 KiB
Python
149 lines
5.6 KiB
Python
"""P0.3 写路径:dispatch/flow 落 sqlite run+events,revise 带 diff,同输入可重跑 diff。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import pathlib
|
||
import subprocess
|
||
import sys
|
||
import tempfile
|
||
import unittest
|
||
|
||
|
||
ROOT = next(
|
||
parent
|
||
for parent in (pathlib.Path(__file__).resolve().parent, *pathlib.Path(__file__).resolve().parents)
|
||
if (parent / "AGENTS.md").is_file() and (parent / ".git").exists()
|
||
)
|
||
if str(ROOT) not in sys.path:
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
DISPATCH_SCRIPTS = (
|
||
ROOT / "muse" / "lifecycle" / "dispatch" / "skills" / "dispatch-agent-task" / "scripts"
|
||
)
|
||
EVIDENCE_DIR = ROOT / "muse" / "authority" / "evidence" / "skills" / "record-run-evidence" / "scripts"
|
||
DISPATCH_TESTS = ROOT / "tests" / "skills" / "dispatch-agent-task"
|
||
for path in (DISPATCH_SCRIPTS, EVIDENCE_DIR, DISPATCH_TESTS):
|
||
if str(path) not in sys.path:
|
||
sys.path.insert(0, str(path))
|
||
|
||
from framework.adapters.pi.runner import ExecutionPolicy # noqa: E402
|
||
from muse.flow.dispatch import run_dispatch # noqa: E402
|
||
from muse.store import add_review, connect, get_run, list_events # noqa: E402
|
||
from test_dispatch_agent_task import ( # noqa: E402
|
||
RecordingConnect,
|
||
fake_launcher,
|
||
make_spec,
|
||
pi_stream_lines,
|
||
)
|
||
|
||
|
||
class SqliteWritePathTest(unittest.TestCase):
|
||
def setUp(self) -> None:
|
||
self.tmp = pathlib.Path(tempfile.mkdtemp())
|
||
self.db = self.tmp / "muse.db"
|
||
self.spec = make_spec(self.tmp)
|
||
|
||
def _dispatch(self, run_id: str, run_dir: pathlib.Path):
|
||
return run_dispatch(
|
||
self.spec,
|
||
repo_root=ROOT,
|
||
policy=ExecutionPolicy(provider="p", model="claude-opus-test"),
|
||
run_id=run_id,
|
||
run_dir=run_dir,
|
||
connect_factory=RecordingConnect(),
|
||
launcher=fake_launcher(
|
||
pi_stream_lines('{"title":"重启","beats":["警报","分歧","决断"]}')
|
||
),
|
||
trigger_source="diagnostic",
|
||
sqlite_path=self.db,
|
||
)
|
||
|
||
def test_write_path_run_events_revise_and_rerun_diff(self) -> None:
|
||
before = subprocess.check_output(["git", "status", "--short"], cwd=ROOT, text=True)
|
||
receipt, code = self._dispatch("p03-write-1", self.tmp / "run-1")
|
||
self.assertEqual(code, 0, receipt)
|
||
self.assertEqual(receipt["status"], "completed")
|
||
|
||
row = get_run("p03-write-1", self.db)
|
||
self.assertIsNotNone(row)
|
||
self.assertEqual(row["kind"], "agent.planner")
|
||
self.assertIn("重启", row["output_text"])
|
||
events = list_events("p03-write-1", self.db)
|
||
kinds = [item["kind"] for item in events]
|
||
self.assertIn("run.started", kinds)
|
||
self.assertIn("run.completed", kinds)
|
||
self.assertTrue(any(kind.startswith("model.") or kind == "agent.started" for kind in kinds))
|
||
|
||
with connect(self.db) as conn:
|
||
mode = conn.execute("PRAGMA journal_mode").fetchone()[0]
|
||
self.assertEqual(str(mode).lower(), "wal")
|
||
|
||
add_review(
|
||
run_id="p03-write-1",
|
||
target="candidate",
|
||
action="revise",
|
||
reviewer="qingse",
|
||
reason="钩子再紧一寸",
|
||
before_text=row["output_text"],
|
||
after_text='{"title":"重启","beats":["警报","摊牌","决断"]}',
|
||
path=self.db,
|
||
)
|
||
with connect(self.db) as conn:
|
||
review = conn.execute(
|
||
"SELECT * FROM reviews WHERE run_id=?", ("p03-write-1",)
|
||
).fetchone()
|
||
revision = conn.execute(
|
||
"SELECT * FROM revisions WHERE review_id=?", (review["id"],)
|
||
).fetchone()
|
||
self.assertEqual(review["action"], "revise")
|
||
self.assertNotEqual(revision["before_text"], revision["after_text"])
|
||
|
||
receipt2, code2 = self._dispatch("p03-write-2", self.tmp / "run-2")
|
||
self.assertEqual(code2, 0, receipt2)
|
||
replay = get_run("p03-write-2", self.db)
|
||
first_input = json.loads(row["input_json"])
|
||
second_input = json.loads(replay["input_json"])
|
||
self.assertEqual(first_input["specSha256"], second_input["specSha256"])
|
||
self.assertEqual(row["output_text"], replay["output_text"])
|
||
|
||
after = subprocess.check_output(["git", "status", "--short"], cwd=ROOT, text=True)
|
||
self.assertEqual(before, after)
|
||
ignored = subprocess.run(
|
||
["git", "check-ignore", "-q", "data/muse.db"],
|
||
cwd=ROOT,
|
||
)
|
||
self.assertEqual(ignored.returncode, 0)
|
||
|
||
def test_search_and_freeze_recall_share_sqlite_vectors(self) -> None:
|
||
from muse.store import upsert_card
|
||
|
||
freeze_scripts = ROOT / "muse" / "lifecycle" / "context" / "skills" / "freeze-context" / "scripts"
|
||
search_scripts = ROOT / "muse" / "lifecycle" / "context" / "skills" / "search-knowledge" / "scripts"
|
||
sys.path.insert(0, str(freeze_scripts))
|
||
sys.path.insert(0, str(search_scripts))
|
||
import recall
|
||
from search import search_cards
|
||
|
||
upsert_card(
|
||
card_id="c1",
|
||
kind="craft",
|
||
title="递进揭示",
|
||
payload={"型": "craft", "名称": "递进揭示", "一句话摘要": "逐层加压"},
|
||
embedding=[1.0, 0.0],
|
||
path=self.db,
|
||
)
|
||
cards = search_cards(
|
||
"揭示",
|
||
sqlite_path=self.db,
|
||
embedder=lambda _intent: [1.0, 0.0],
|
||
)
|
||
recalled = recall.recall_cards([1.0, 0.0], kind="craft", sqlite_path=self.db)
|
||
self.assertEqual(cards[0]["cardId"], "c1")
|
||
self.assertEqual(recalled[0]["cardId"], "c1")
|
||
self.assertAlmostEqual(cards[0]["score"], 1.0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|