@matt82198/aesop 0.1.0-rc.1 → 0.2.0

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +101 -2
  2. package/README.md +66 -15
  3. package/bin/cli.js +164 -41
  4. package/daemons/run-watchdog.sh +16 -4
  5. package/daemons/selfheal.sh +231 -0
  6. package/docs/ANY-REPO.md +427 -0
  7. package/docs/CONTRIBUTING.md +72 -0
  8. package/docs/DEMO.md +334 -0
  9. package/docs/INSTALL.md +70 -1
  10. package/docs/QUICKSTART.md +80 -0
  11. package/docs/README.md +33 -3
  12. package/docs/TEAM-STATE.md +540 -0
  13. package/driver/CLAUDE.md +148 -0
  14. package/driver/README.md +383 -0
  15. package/driver/aesop.config.example.json +80 -0
  16. package/driver/agent_driver.py +355 -0
  17. package/driver/backend_config.py +253 -0
  18. package/driver/claude_code_driver.py +198 -0
  19. package/driver/codex_driver.py +626 -0
  20. package/driver/openai_compatible_driver.py +249 -0
  21. package/driver/openai_transport.py +146 -0
  22. package/driver/verification_policy.py +75 -0
  23. package/driver/wave_bridge.py +246 -0
  24. package/driver/wave_loop.py +1041 -0
  25. package/hooks/pre-push-policy.sh +109 -28
  26. package/mcp/server.mjs +320 -4
  27. package/package.json +23 -15
  28. package/skills/CLAUDE.md +132 -2
  29. package/skills/buildsystem/SKILL.md +330 -0
  30. package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
  31. package/skills/fleet/SKILL.md +113 -0
  32. package/skills/power/SKILL.md +246 -131
  33. package/state_store/__init__.py +2 -1
  34. package/state_store/api.py +19 -4
  35. package/state_store/coordination.py +209 -0
  36. package/state_store/identity.py +51 -0
  37. package/state_store/store.py +185 -73
  38. package/templates/wave-presets/data.json +65 -0
  39. package/templates/wave-presets/library.json +65 -0
  40. package/templates/wave-presets/saas.json +64 -0
  41. package/tools/audit_report.py +388 -0
  42. package/tools/bench_runner.py +100 -3
  43. package/tools/ci_merge_wait.py +256 -35
  44. package/tools/ci_workflow_lint.py +430 -0
  45. package/tools/claudemd_drift.py +394 -0
  46. package/tools/claudemd_lint.py +359 -0
  47. package/tools/common.py +39 -3
  48. package/tools/cost_ceiling.py +63 -31
  49. package/tools/cost_econ.py +480 -0
  50. package/tools/defect_escape.py +252 -0
  51. package/tools/doctor.js +1 -1
  52. package/tools/eod_sweep.py +58 -0
  53. package/tools/fleet.js +260 -0
  54. package/tools/fleet_ledger.py +209 -7
  55. package/tools/git_identity_check.py +315 -0
  56. package/tools/health-score.js +40 -0
  57. package/tools/health_score.py +361 -0
  58. package/tools/metrics_gate.py +13 -4
  59. package/tools/mutation_test.py +401 -0
  60. package/tools/portability_check.py +206 -0
  61. package/tools/reconcile.py +7 -4
  62. package/tools/secret_scan.py +137 -50
  63. package/tools/self_stats.py +20 -0
  64. package/tools/transcript_digest.py +380 -0
  65. package/tools/verify_activity_filter.py +437 -0
  66. package/tools/verify_agent_inspector.py +2 -0
  67. package/tools/verify_dash.py +2 -0
  68. package/tools/verify_dispatch_panel.py +301 -0
  69. package/tools/verify_failure_drilldown.py +188 -0
  70. package/tools/verify_prboard.py +2 -0
  71. package/tools/verify_scorecards.py +281 -0
  72. package/tools/verify_submit_encoding.py +2 -0
  73. package/tools/verify_ui_trio.py +409 -0
  74. package/tools/verify_wave_telemetry.py +268 -0
  75. package/tools/wave_backlog_analyzer.py +490 -0
  76. package/tools/wave_ledger_hook.py +150 -0
  77. package/tools/wave_preflight.py +512 -0
  78. package/tools/wave_resume.py +215 -0
  79. package/tools/wave_templates.py +215 -0
  80. package/ui/agents.py +68 -14
  81. package/ui/collectors.py +0 -55
  82. package/ui/config.py +7 -2
  83. package/ui/cost.py +231 -12
  84. package/ui/handler.py +183 -0
  85. package/ui/quality_scorecard.py +232 -0
  86. package/ui/wave_audit_tail.py +213 -0
  87. package/ui/wave_dispatch.py +280 -0
  88. package/ui/wave_failure.py +288 -0
  89. package/ui/wave_gantt.py +152 -0
  90. package/ui/wave_reasoning_tail.py +176 -0
  91. package/ui/wave_telemetry.py +377 -0
  92. package/ui/web/dist/assets/index-BGbgw2Nh.js +9 -0
  93. package/ui/web/dist/assets/index-DqZLgwNg.css +1 -0
  94. package/ui/web/dist/index.html +2 -2
  95. package/bin/CLAUDE.md +0 -76
  96. package/daemons/CLAUDE.md +0 -36
  97. package/dash/CLAUDE.md +0 -32
  98. package/docs/archive/README.md +0 -3
  99. package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
  100. package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
  101. package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
  102. package/docs/archive/spikes/tiered-cognition/README.md +0 -27
  103. package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
  104. package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
  105. package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
  106. package/hooks/CLAUDE.md +0 -89
  107. package/mcp/CLAUDE.md +0 -213
  108. package/monitor/CLAUDE.md +0 -40
  109. package/scan/CLAUDE.md +0 -30
  110. package/state_store/CLAUDE.md +0 -39
  111. package/tools/CLAUDE.md +0 -79
  112. package/ui/CLAUDE.md +0 -127
  113. package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
  114. package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
@@ -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
+ }