Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/skill-tests/test_dream_sessions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Fixture tests for Dream's local JSONL session extractor."""

import importlib.util
import json
import tempfile
import time
from pathlib import Path

SCRIPT = Path(__file__).resolve().parents[2] / "agents/skills/dream/scripts/sessions.py"
spec = importlib.util.spec_from_file_location("dream_sessions", SCRIPT)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)


def write_jsonl(path, records):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(json.dumps(record) for record in records) + "\n", encoding="utf-8")


def codex_message(role, value, phase=None):
return {"type": "response_item", "payload": {"type": "message", "role": role,
"phase": phase, "content": [{"type": "input_text" if role == "user" else "output_text",
"text": value}]}}


def claude_message(role, value, stop_reason=None, **extra):
return {"type": role, "message": {"role": role, "content": value,
"stop_reason": stop_reason}, **extra}


def run():
with tempfile.TemporaryDirectory() as root:
root = Path(root)
claude = root / "claude"
codex = root / "codex"
state = root / "runs/state.json"
codex_id = "codex-session-1234"
claude_id = "claude-session-1234"
codex_file = codex / "2026/09/24" / f"rollout-{codex_id}.jsonl"
claude_file = claude / "project" / f"{claude_id}.jsonl"
secret = "sk-" + "A" * 25
extra_secrets = ["SG." + "A" * 16 + "." + "B" * 16,
"ntn_" + "A" * 20, "sntrys_" + "A" * 20,
"dda" + "a" * 20, "pdkey_" + "A" * 16]
write_jsonl(codex_file, [
{"type": "session_meta", "payload": {"id": codex_id, "cwd": str(root / "work")}},
codex_message("user", f"Choose SQLite. {secret} {' '.join(extra_secrets)}"),
{"type": "response_item", "payload": {"type": "reasoning", "content": "private reasoning"}},
codex_message("assistant", "Use SQLite for the cache.", "final_answer"),
{"type": "event_msg", "payload": {"type": "task_complete"}},
codex_message("user", "unfinished Codex turn"),
])
write_jsonl(claude_file, [
{**claude_message("user", "Choose Postgres."), "cwd": str(root / "work")},
claude_message("assistant", [{"type": "tool_use", "name": "Bash"}], "tool_use"),
claude_message("user", [{"type": "tool_result", "content": "private tool output"}]),
claude_message("assistant", [{"type": "text", "text": "Use Postgres for storage."}], "end_turn"),
claude_message("user", "ignore this", isSidechain=True),
claude_message("user", "unfinished Claude turn"),
])
sources = (("claude", claude), ("codex", codex))
found = list(module.pending(sources, state))
assert len(found) == 2, found
assert {item["source"] for item in found} == {"claude", "codex"}
assert not state.exists(), "discovery and dry-run must not write state"
captures = "\n".join(item["capture"] for item in found)
assert "session-capture, claude, transcript" in captures
assert "session-capture, codex, transcript" in captures
assert "no-commit" not in captures
assert "Use SQLite for the cache." in captures
assert "Use Postgres for storage." in captures
assert "[REDACTED-API-KEY]" in captures and secret not in captures
assert all(secret_value not in captures for secret_value in extra_secrets)
assert "private reasoning" not in captures and "private tool output" not in captures
assert "unfinished" not in captures
for item in found:
module.mark(state, item["source"], item["session_id"], item["turn"],
item["transcript_path"], item["last_turn"], item["mtime_ns"])
assert list(module.pending(sources, state)) == []
with codex_file.open("a", encoding="utf-8") as transcript:
transcript.write(json.dumps(codex_message("assistant", "New answer.", "final_answer")) + "\n")
transcript.write(json.dumps({"type": "event_msg", "payload": {"type": "task_complete"}}) + "\n")
transcript.write("{unfinished json")
found = list(module.pending(sources, state))
assert len(found) == 1 and found[0]["turn"] == 2, found
assert "New answer." in found[0]["capture"]
gap_state = root / "runs/gap.json"
module.mark(gap_state, "codex", codex_id, 2, codex_file, 2, found[0]["mtime_ns"])
assert [item["turn"] for item in module.pending((("codex", codex),), gap_state)] == [1]
with claude_file.open("a", encoding="utf-8") as transcript:
for turn in range(11):
transcript.write(json.dumps(claude_message("user", f"Request {turn}")) + "\n")
transcript.write(json.dumps(claude_message("assistant", f"Answer {turn}", "end_turn")) + "\n")
found = list(module.pending(sources, state))
assert len(found) == 11, "Claude backlog must leave room for Codex"
assert sum(item["source"] == "claude" for item in found) == 10
assert sum(item["source"] == "codex" for item in found) == 1
assert list(module.pending((("claude", root / "missing"),), state)) == []
old = time.time() - 7 * 86400
import os
os.utime(codex_file, (old, old))
assert len(list(module.pending((("codex", codex),), state))) == 1, "old Codex turns remain eligible"
exclude_file = root / "excluded.txt"
exclude_file.write_text("work\n", encoding="utf-8")
assert list(module.pending(sources, state, exclude_file)) == []
empty_final = claude / "project" / "empty-final-1234.jsonl"
write_jsonl(empty_final, [
claude_message("user", "First request"),
claude_message("assistant", [], "end_turn"),
claude_message("user", "Second request"),
claude_message("assistant", "Second answer", "end_turn"),
])
turns = list(module.read_turns(empty_final, "claude"))
assert [turn for _, turn, _ in turns] == [1, 2]
print("dream session extractor: passed")


if __name__ == "__main__":
run()
3 changes: 3 additions & 0 deletions .github/skill-tests/test_dream_sessions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
set -euo pipefail
python3 "$(dirname "$0")/test_dream_sessions.py"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ sys
.claude/worktrees
pi/agent/auth.json
pi/agent/sessions/
.context/
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ dots docker stop-all # Stop all Docker containers

| Component | What it installs |
|-----------|------------------|
| `agents` | Agent skills, custom agents, hooks, and status line (symlinks `agents/skills/` → `~/.claude/skills/` + `~/.agents/skills/`, `agents/custom/` → `~/.claude/agents/`, registers SessionStart/SessionEnd/PostToolUse hooks and status line in `~/.claude/settings.json`; one SessionStart hook prepends dev-tool bin dirs — `go/bin`, cargo, asdf shims — onto the Bash tool's `PATH` so binaries like `tts` resolve by bare name) |
| `agents` | Agent skills, custom agents, hooks, and status line (symlinks `agents/skills/` → `~/.claude/skills/` + `~/.agents/skills/`, `agents/custom/` → `~/.claude/agents/`, registers SessionStart/PostToolUse hooks and status line in `~/.claude/settings.json`; one SessionStart hook prepends dev-tool bin dirs — `go/bin`, cargo, asdf shims — onto the Bash tool's `PATH` so binaries like `tts` resolve by bare name) |
| `bin` | Custom shell scripts and Go utilities to `~/bin` |
| `git` | `.gitconfig`, `.gitignore_global`, git extensions |
| `home` | Dotfiles symlinked to `~/` (`.zshrc`, `.vimrc`, `.tmux.conf`, `.gitconfig`, etc.) |
Expand Down Expand Up @@ -105,7 +105,7 @@ Dots includes 72 reusable slash-command skills for AI coding agents, following t
| `/standup` | Daily standup summary from git activity |
| `/pdf` | Export conversation content to styled PDF |
| `/knowledge` | Initialize or update a project knowledge base |
| `/dream` | Ingest meetings + session captures into the inbox, synthesize raw notes into existing topical docs, and audit KB hygiene — frontmatter, sizing, naming |
| `/dream` | Ingest meetings and completed Claude Code/Codex JSONL turns into the inbox, synthesize raw notes into existing topical docs, and audit KB hygiene — frontmatter, sizing, naming |
| `/retro` | Structured retrospective or post-incident review |
| `/logo` | Logo generation |
| `/slides` | Build a self-contained HTML presentation deck from talking points or a doc, with keyboard, tap, and swipe navigation |
Expand Down Expand Up @@ -265,4 +265,4 @@ GitHub Actions runs on every push to master and all PRs:

## License

[MIT License](LICENSE.md)
[MIT License](LICENSE.md)
Loading
Loading