@matt82198/aesop 0.1.0 → 0.3.1
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/CHANGELOG.md +117 -2
- package/README.md +59 -218
- package/bin/cli.js +168 -41
- package/daemons/run-watchdog.sh +16 -4
- package/daemons/selfheal.sh +231 -0
- package/docs/ANY-REPO.md +427 -0
- package/docs/CONTRIBUTING.md +72 -0
- package/docs/DEMO.md +334 -0
- package/docs/HOOK-INSTALL.md +15 -56
- package/docs/INSTALL.md +74 -4
- package/docs/PORTING.md +166 -0
- package/docs/README.md +33 -3
- package/docs/TEAM-STATE.md +372 -0
- package/docs/reproduce.md +33 -3
- package/driver/CLAUDE.md +150 -0
- package/driver/README.md +383 -0
- package/driver/aesop.config.example.json +80 -0
- package/driver/agent_driver.py +355 -0
- package/driver/backend_config.py +253 -0
- package/driver/claude_code_driver.py +198 -0
- package/driver/codex_driver.py +673 -0
- package/driver/openai_compatible_driver.py +249 -0
- package/driver/openai_transport.py +179 -0
- package/driver/verification_policy.py +75 -0
- package/driver/wave_bridge.py +254 -0
- package/driver/wave_loop.py +1408 -0
- package/driver/wave_scheduler.py +890 -0
- package/hooks/pre-push-policy.sh +131 -33
- package/mcp/server.mjs +320 -4
- package/monitor/collect-signals.mjs +69 -0
- package/package.json +22 -14
- package/skills/CLAUDE.md +132 -2
- package/skills/buildsystem/SKILL.md +330 -0
- package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
- package/skills/fleet/SKILL.md +113 -0
- package/skills/power/SKILL.md +246 -131
- package/state_store/__init__.py +4 -2
- package/state_store/api.py +19 -4
- package/state_store/coordination.py +209 -0
- package/state_store/identity.py +51 -0
- package/state_store/projections.py +63 -0
- package/state_store/read_api.py +156 -0
- package/state_store/store.py +185 -73
- package/state_store/write_api.py +462 -0
- package/templates/wave-presets/data.json +65 -0
- package/templates/wave-presets/library.json +65 -0
- package/templates/wave-presets/saas.json +64 -0
- package/tools/audit_report.py +388 -0
- package/tools/bench_runner.py +100 -3
- package/tools/ci_merge_wait.py +256 -35
- package/tools/ci_workflow_lint.py +430 -0
- package/tools/claudemd_drift.py +394 -0
- package/tools/claudemd_lint.py +359 -0
- package/tools/common.py +39 -3
- package/tools/cost_ceiling.py +166 -43
- package/tools/cost_econ.py +480 -0
- package/tools/cost_projection.py +559 -0
- package/tools/crossos_drift.py +394 -0
- package/tools/defect_escape.py +252 -0
- package/tools/doctor.js +1 -1
- package/tools/eod_sweep.py +188 -26
- package/tools/fleet.js +260 -0
- package/tools/fleet_ledger.py +209 -7
- package/tools/git_identity_check.py +315 -0
- package/tools/health-score.js +40 -0
- package/tools/health_score.py +361 -0
- package/tools/metrics_gate.py +13 -4
- package/tools/mutation_test.py +523 -0
- package/tools/portability_check.py +206 -0
- package/tools/proposals.mjs +47 -2
- package/tools/reconcile.py +7 -4
- package/tools/reproduce.js +405 -0
- package/tools/secret_scan.py +207 -65
- package/tools/self_stats.py +20 -0
- package/tools/stall_check.py +247 -16
- package/tools/stateapi_lint.py +325 -0
- package/tools/test_battery.py +173 -0
- package/tools/transcript_digest.py +380 -0
- package/tools/verify_activity_filter.py +437 -0
- package/tools/verify_agent_inspector.py +2 -0
- package/tools/verify_cost_panel.py +345 -0
- package/tools/verify_dash.py +2 -0
- package/tools/verify_dispatch_panel.py +301 -0
- package/tools/verify_failure_drilldown.py +188 -0
- package/tools/verify_prboard.py +2 -0
- package/tools/verify_scorecards.py +281 -0
- package/tools/verify_submit_encoding.py +2 -0
- package/tools/verify_ui_trio.py +409 -0
- package/tools/verify_wave_telemetry.py +268 -0
- package/tools/wave_backlog_analyzer.py +490 -0
- package/tools/wave_ledger_hook.py +150 -0
- package/tools/wave_preflight.py +779 -0
- package/tools/wave_resume.py +215 -0
- package/tools/wave_templates.py +340 -0
- package/ui/agents.py +68 -14
- package/ui/collectors.py +81 -55
- package/ui/config.py +7 -2
- package/ui/cost.py +231 -12
- package/ui/handler.py +383 -55
- package/ui/quality_scorecard.py +232 -0
- package/ui/sse.py +3 -3
- package/ui/wave_audit_tail.py +213 -0
- package/ui/wave_dispatch.py +280 -0
- package/ui/wave_failure.py +288 -0
- package/ui/wave_gantt.py +152 -0
- package/ui/wave_reasoning_tail.py +176 -0
- package/ui/wave_telemetry.py +383 -0
- package/ui/web/dist/assets/index-CNQxaiOW.css +1 -0
- package/ui/web/dist/assets/index-CP68RIh3.js +9 -0
- package/ui/web/dist/index.html +2 -2
- package/bin/CLAUDE.md +0 -76
- package/daemons/CLAUDE.md +0 -36
- package/dash/CLAUDE.md +0 -32
- package/docs/archive/README.md +0 -3
- package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
- package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
- package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
- package/docs/archive/spikes/tiered-cognition/README.md +0 -27
- package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
- package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
- package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
- package/hooks/CLAUDE.md +0 -89
- package/mcp/CLAUDE.md +0 -213
- package/monitor/CLAUDE.md +0 -40
- package/scan/CLAUDE.md +0 -30
- package/state_store/CLAUDE.md +0 -39
- package/tools/CLAUDE.md +0 -79
- package/ui/CLAUDE.md +0 -127
- package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
- package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
package/ui/wave_gantt.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Wave Gantt timeline data — per-agent phase spans from ledger/journal + transcript mtimes.
|
|
4
|
+
|
|
5
|
+
Reads agent dispatch data (via wave_dispatch.py) and enriches with timing info
|
|
6
|
+
from the ledger to create Gantt-style bars (agent rows, phase spans as bars).
|
|
7
|
+
|
|
8
|
+
Returns timeline data for visualization in the Activity view.
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
import config
|
|
16
|
+
import wave_dispatch
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _parse_agent_phases_from_transcript(agent_id: str) -> List[Dict]:
|
|
20
|
+
"""Extract phase timing info from agent transcript (if available).
|
|
21
|
+
|
|
22
|
+
Looks for transcript_mtime events in state_store that track phase changes.
|
|
23
|
+
Falls back to dispatch inference if not available.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
list: [{"phase": "dispatch", "start": iso_timestamp, "end": iso_timestamp, ...}, ...]
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
# For now, use dispatch data as single span
|
|
30
|
+
# Future: parse state_store for detailed phase tracking
|
|
31
|
+
dispatch_data = wave_dispatch.get_wave_dispatch()
|
|
32
|
+
if not dispatch_data or not dispatch_data.get("available"):
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
# Find this agent in dispatch data
|
|
36
|
+
for agent in dispatch_data.get("agents", []):
|
|
37
|
+
if agent.get("id") == agent_id:
|
|
38
|
+
# Create a single span from start to now
|
|
39
|
+
now = datetime.now(timezone.utc).isoformat() + "Z"
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
"phase": agent.get("phase", "unknown"),
|
|
43
|
+
"start": agent.get("started_at") or now,
|
|
44
|
+
"end": agent.get("last_activity") or now,
|
|
45
|
+
"duration_sec": agent.get("last_activity_age_sec", 0),
|
|
46
|
+
"token_estimate": agent.get("token_estimate", 0),
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
return []
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"[wave_gantt] Error parsing phases: {e}")
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_wave_gantt() -> Dict:
|
|
56
|
+
"""Get Gantt timeline data for current wave.
|
|
57
|
+
|
|
58
|
+
Returns per-agent rows with phase timing spans suitable for Gantt visualization.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict: {
|
|
62
|
+
"available": bool,
|
|
63
|
+
"wave_phase": str,
|
|
64
|
+
"agents": [
|
|
65
|
+
{
|
|
66
|
+
"id": "agent-123",
|
|
67
|
+
"phases": [
|
|
68
|
+
{"phase": "dispatch", "start": iso, "end": iso, "duration_sec": 10},
|
|
69
|
+
{"phase": "thinking", "start": iso, "end": iso, "duration_sec": 25},
|
|
70
|
+
...
|
|
71
|
+
],
|
|
72
|
+
"total_duration_sec": 120,
|
|
73
|
+
"status": "running|done|error"
|
|
74
|
+
},
|
|
75
|
+
...
|
|
76
|
+
],
|
|
77
|
+
"at": "2026-07-21T12:34:56Z"
|
|
78
|
+
}
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
dispatch_data = wave_dispatch.get_wave_dispatch()
|
|
82
|
+
if not dispatch_data or not dispatch_data.get("available"):
|
|
83
|
+
return {
|
|
84
|
+
"available": False,
|
|
85
|
+
"agents": [],
|
|
86
|
+
"error": "No active workflow",
|
|
87
|
+
"at": datetime.now(timezone.utc).isoformat() + "Z"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
agents = []
|
|
91
|
+
for agent in dispatch_data.get("agents", []):
|
|
92
|
+
agent_id = agent.get("id", "unknown")
|
|
93
|
+
phases = _parse_agent_phases_from_transcript(agent_id)
|
|
94
|
+
|
|
95
|
+
# Calculate total duration
|
|
96
|
+
total_sec = 0
|
|
97
|
+
if phases:
|
|
98
|
+
# Use the span from earliest start to latest end
|
|
99
|
+
starts = [p.get("start") for p in phases if p.get("start")]
|
|
100
|
+
ends = [p.get("end") for p in phases if p.get("end")]
|
|
101
|
+
if starts and ends:
|
|
102
|
+
try:
|
|
103
|
+
earliest = min(datetime.fromisoformat(s.replace("Z", "+00:00")) for s in starts)
|
|
104
|
+
latest = max(datetime.fromisoformat(e.replace("Z", "+00:00")) for e in ends)
|
|
105
|
+
total_sec = int((latest - earliest).total_seconds())
|
|
106
|
+
except (ValueError, TypeError):
|
|
107
|
+
total_sec = sum(p.get("duration_sec", 0) for p in phases)
|
|
108
|
+
else:
|
|
109
|
+
# Fallback: use last_activity_age_sec
|
|
110
|
+
total_sec = agent.get("last_activity_age_sec", 0)
|
|
111
|
+
|
|
112
|
+
agents.append({
|
|
113
|
+
"id": agent_id,
|
|
114
|
+
"phases": phases or [
|
|
115
|
+
{
|
|
116
|
+
"phase": agent.get("phase", "unknown"),
|
|
117
|
+
"start": agent.get("started_at") or datetime.now(timezone.utc).isoformat() + "Z",
|
|
118
|
+
"end": agent.get("last_activity") or datetime.now(timezone.utc).isoformat() + "Z",
|
|
119
|
+
"duration_sec": agent.get("last_activity_age_sec", 0),
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"total_duration_sec": total_sec,
|
|
123
|
+
"status": _infer_status(agent.get("phase", "unknown"), agent.get("warnings", [])),
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"available": True,
|
|
128
|
+
"wave_phase": dispatch_data.get("wave_phase", "unknown"),
|
|
129
|
+
"agents": agents,
|
|
130
|
+
"at": dispatch_data.get("at", datetime.now(timezone.utc).isoformat() + "Z"),
|
|
131
|
+
}
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f"[wave_gantt] Uncaught error: {e}")
|
|
134
|
+
import traceback
|
|
135
|
+
traceback.print_exc()
|
|
136
|
+
return {
|
|
137
|
+
"available": False,
|
|
138
|
+
"agents": [],
|
|
139
|
+
"error": str(e),
|
|
140
|
+
"at": datetime.now(timezone.utc).isoformat() + "Z"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _infer_status(phase: str, warnings: List[str]) -> str:
|
|
145
|
+
"""Infer agent status from phase and warnings."""
|
|
146
|
+
if "stall" in phase.lower():
|
|
147
|
+
return "stalled"
|
|
148
|
+
if warnings and any("inactive" in w.lower() for w in warnings):
|
|
149
|
+
return "inactive"
|
|
150
|
+
if phase.lower() == "done":
|
|
151
|
+
return "done"
|
|
152
|
+
return "running"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Wave reasoning tail — per-agent live transcript activity summary.
|
|
4
|
+
|
|
5
|
+
Shows latest reasoning/transcript activity for each live agent in a compact,
|
|
6
|
+
redacted format. Reuses transcript_digest.py's redaction + summarization patterns.
|
|
7
|
+
|
|
8
|
+
Returns reasoning tail data for the Activity view.
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
import config
|
|
18
|
+
import wave_dispatch
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Redaction patterns (from transcript_digest.py, simplified)
|
|
22
|
+
def redact_text(text: str) -> str:
|
|
23
|
+
"""Aggressively redact secrets, paths, emails from transcript text."""
|
|
24
|
+
if not text:
|
|
25
|
+
return text
|
|
26
|
+
|
|
27
|
+
# Redact common secrets
|
|
28
|
+
text = re.sub(r'sk-[A-Za-z0-9_\-]{20,}', '[REDACTED]', text) # OpenAI/Anthropic keys
|
|
29
|
+
text = re.sub(r'ghp_[A-Za-z0-9_]{20,}', '[REDACTED]', text) # GitHub tokens
|
|
30
|
+
text = re.sub(r'AKIA[0-9A-Z]{16}', '[REDACTED]', text) # AWS keys
|
|
31
|
+
|
|
32
|
+
# Redact paths (Windows and POSIX)
|
|
33
|
+
text = re.sub(r'[A-Za-z]:\\[^\\/:*<>|]*', '[PATH]', text) # Windows paths
|
|
34
|
+
text = re.sub(r'/[^/:*<>|]*', '[PATH]', text) # POSIX paths
|
|
35
|
+
|
|
36
|
+
# Redact emails
|
|
37
|
+
text = re.sub(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', '[EMAIL]', text)
|
|
38
|
+
|
|
39
|
+
# Redact usernames
|
|
40
|
+
text = re.sub(r'\b(?:matt8|matt82198|John|Jack)\b', '[USER]', text, flags=re.IGNORECASE)
|
|
41
|
+
|
|
42
|
+
# Redact repo names
|
|
43
|
+
text = re.sub(r'\b(?:aesop|conductor3|tr-sample-tracker|ecm-ai)\b', '[REPO]', text, flags=re.IGNORECASE)
|
|
44
|
+
|
|
45
|
+
return text
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _extract_agent_reasoning(agent_id: str) -> Optional[str]:
|
|
49
|
+
"""Extract latest reasoning/activity summary from agent transcript.
|
|
50
|
+
|
|
51
|
+
Looks for the agent's transcript in ~/.claude/projects/*/memory/agent-*.jsonl
|
|
52
|
+
and extracts the latest thinking/tool-call sequence as a brief summary.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Brief summary string (redacted) or None if transcript not found/readable.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
transcripts_root = config.TRANSCRIPTS_ROOT
|
|
59
|
+
if not transcripts_root or not transcripts_root.exists():
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# Search for agent transcript file matching this agent_id
|
|
63
|
+
for jsonl_file in transcripts_root.glob('**/agent-*.jsonl'):
|
|
64
|
+
# Check if this agent file matches
|
|
65
|
+
if agent_id not in str(jsonl_file):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Read last few lines (most recent messages)
|
|
69
|
+
try:
|
|
70
|
+
lines = jsonl_file.read_text(encoding='utf-8', errors='ignore').split('\n')
|
|
71
|
+
recent_lines = [l for l in lines if l.strip()][-5:] # Last 5 messages
|
|
72
|
+
|
|
73
|
+
if not recent_lines:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
# Extract and summarize message types
|
|
77
|
+
summary_parts = []
|
|
78
|
+
for line in recent_lines:
|
|
79
|
+
try:
|
|
80
|
+
msg = json.loads(line)
|
|
81
|
+
msg_type = msg.get('type', 'unknown')
|
|
82
|
+
role = msg.get('role', msg_type)
|
|
83
|
+
|
|
84
|
+
# Extract brief content
|
|
85
|
+
content = msg.get('content', '')
|
|
86
|
+
if isinstance(content, list):
|
|
87
|
+
content = str(content)[:50]
|
|
88
|
+
elif isinstance(content, str):
|
|
89
|
+
content = content[:50]
|
|
90
|
+
|
|
91
|
+
if msg_type == 'tool_use':
|
|
92
|
+
summary_parts.append(f'tool:{msg.get("name", "?")[:8]}')
|
|
93
|
+
elif msg_type == 'tool_result':
|
|
94
|
+
summary_parts.append('result')
|
|
95
|
+
elif role == 'assistant':
|
|
96
|
+
summary_parts.append('thinking')
|
|
97
|
+
elif role == 'user':
|
|
98
|
+
summary_parts.append('prompt')
|
|
99
|
+
except (json.JSONDecodeError, ValueError):
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
if summary_parts:
|
|
103
|
+
summary = ' → '.join(summary_parts)
|
|
104
|
+
return redact_text(summary)
|
|
105
|
+
|
|
106
|
+
except (OSError, IOError):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
return None
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print(f"[wave_reasoning_tail] Error extracting reasoning: {e}")
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_wave_reasoning_tail() -> Dict:
|
|
116
|
+
"""Get reasoning tail data for live agents in current wave.
|
|
117
|
+
|
|
118
|
+
Returns per-agent latest transcript activity summary (redacted).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
dict: {
|
|
122
|
+
"available": bool,
|
|
123
|
+
"agents": [
|
|
124
|
+
{
|
|
125
|
+
"id": "agent-id",
|
|
126
|
+
"phase": "dispatch|thinking|tool-use|stall|done",
|
|
127
|
+
"reasoning": "brief redacted summary",
|
|
128
|
+
"activity_age_sec": int,
|
|
129
|
+
"token_estimate": int,
|
|
130
|
+
},
|
|
131
|
+
...
|
|
132
|
+
],
|
|
133
|
+
"at": "2026-07-21T12:34:56Z"
|
|
134
|
+
}
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
dispatch_data = wave_dispatch.get_wave_dispatch()
|
|
138
|
+
if not dispatch_data or not dispatch_data.get("available"):
|
|
139
|
+
return {
|
|
140
|
+
"available": False,
|
|
141
|
+
"error": "No active workflow",
|
|
142
|
+
"agents": [],
|
|
143
|
+
"at": datetime.now(timezone.utc).isoformat() + "Z"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
agents = []
|
|
147
|
+
for agent in dispatch_data.get("agents", []):
|
|
148
|
+
agent_id = agent.get("id", "unknown")
|
|
149
|
+
|
|
150
|
+
# Extract reasoning/activity summary
|
|
151
|
+
reasoning = _extract_agent_reasoning(agent_id)
|
|
152
|
+
|
|
153
|
+
agents.append({
|
|
154
|
+
"id": agent_id,
|
|
155
|
+
"phase": agent.get("phase", "unknown"),
|
|
156
|
+
"reasoning": reasoning or "(no recent activity)",
|
|
157
|
+
"activity_age_sec": agent.get("last_activity_age_sec", 0),
|
|
158
|
+
"token_estimate": agent.get("token_estimate", 0),
|
|
159
|
+
"warnings": agent.get("warnings", []),
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
"available": True,
|
|
164
|
+
"agents": agents,
|
|
165
|
+
"at": datetime.now(timezone.utc).isoformat() + "Z",
|
|
166
|
+
}
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print(f"[wave_reasoning_tail] Uncaught error: {e}")
|
|
169
|
+
import traceback
|
|
170
|
+
traceback.print_exc()
|
|
171
|
+
return {
|
|
172
|
+
"available": False,
|
|
173
|
+
"error": str(e),
|
|
174
|
+
"agents": [],
|
|
175
|
+
"at": datetime.now(timezone.utc).isoformat() + "Z"
|
|
176
|
+
}
|