@matt82198/aesop 0.1.0-beta.2 → 0.1.0-beta.4

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 (108) hide show
  1. package/CHANGELOG.md +37 -94
  2. package/CLAUDE-TEMPLATE.md +115 -0
  3. package/README.md +89 -264
  4. package/aesop.config.example.json +27 -26
  5. package/bin/CLAUDE.md +39 -0
  6. package/bin/cli.js +368 -24
  7. package/daemons/CLAUDE.md +33 -0
  8. package/daemons/backup-fleet.sh +92 -21
  9. package/daemons/run-watchdog.sh +90 -10
  10. package/dash/CLAUDE.md +32 -0
  11. package/dash/dash-extra.mjs +194 -21
  12. package/dash/watchdog-gui.sh +23 -6
  13. package/docs/BEHAVIORAL-PR-REVIEW.md +86 -0
  14. package/docs/CHECKPOINTING.md +100 -0
  15. package/docs/DISPATCH-MODEL.md +11 -7
  16. package/docs/FORENSICS.md +204 -0
  17. package/docs/GOVERNANCE.md +4 -2
  18. package/docs/HOOK-INSTALL.md +307 -0
  19. package/docs/HOW-THE-LOOP-WORKS.md +175 -0
  20. package/docs/MEMORY-TEMPLATE.md +61 -0
  21. package/docs/README.md +44 -0
  22. package/docs/RELIABILITY.md +61 -0
  23. package/docs/RESTORE.md +397 -0
  24. package/docs/SCRIPTS-POLICY.md +97 -0
  25. package/docs/spikes/tiered-cognition/ACTIVATION.md +125 -0
  26. package/docs/spikes/tiered-cognition/DESIGN.md +287 -0
  27. package/docs/spikes/tiered-cognition/FINDINGS.md +113 -0
  28. package/docs/spikes/tiered-cognition/README.md +29 -0
  29. package/docs/spikes/tiered-cognition/aesop-cognition.example.md +32 -0
  30. package/docs/spikes/tiered-cognition/force-model-policy.merged.mjs +673 -0
  31. package/docs/spikes/tiered-cognition/strip-tools-hook.mjs +434 -0
  32. package/hooks/CLAUDE.md +38 -0
  33. package/hooks/claude/force-model-policy.mjs +159 -0
  34. package/hooks/pre-push-policy.sh +753 -0
  35. package/monitor/.signal-state.json +3 -0
  36. package/monitor/ACTIONS.log +1 -0
  37. package/monitor/BRIEF.md +24 -0
  38. package/monitor/CHARTER.md +29 -8
  39. package/monitor/CLAUDE.md +40 -0
  40. package/monitor/SIGNALS.json +54 -0
  41. package/monitor/collect-signals.mjs +529 -62
  42. package/package.json +12 -3
  43. package/skills/CLAUDE.md +3 -0
  44. package/skills/power/SKILL.md +161 -0
  45. package/tools/CLAUDE.md +191 -0
  46. package/tools/__pycache__/buildlog.cpython-314.pyc +0 -0
  47. package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
  48. package/tools/__pycache__/ensure_state.cpython-314.pyc +0 -0
  49. package/tools/__pycache__/eod_sweep.cpython-314.pyc +0 -0
  50. package/tools/__pycache__/fleet_ledger.cpython-314.pyc +0 -0
  51. package/tools/__pycache__/heartbeat.cpython-314.pyc +0 -0
  52. package/tools/__pycache__/inbox_drain.cpython-314.pyc +0 -0
  53. package/tools/__pycache__/launch_tui.cpython-314.pyc +0 -0
  54. package/tools/__pycache__/orchestrator_status.cpython-314.pyc +0 -0
  55. package/tools/__pycache__/power_selftest.cpython-314.pyc +0 -0
  56. package/tools/__pycache__/prepublish_scan.cpython-314.pyc +0 -0
  57. package/tools/__pycache__/rotate_logs.cpython-314.pyc +0 -0
  58. package/tools/__pycache__/scanner_selftest.cpython-314.pyc +0 -0
  59. package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
  60. package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
  61. package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
  62. package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
  63. package/tools/agent-forensics.sh +186 -0
  64. package/tools/buildlog.py +182 -0
  65. package/tools/ci_merge_wait.py +204 -0
  66. package/tools/ensure_state.py +94 -0
  67. package/tools/eod_sweep.py +239 -0
  68. package/tools/fleet_ledger.py +272 -0
  69. package/tools/heartbeat.py +131 -0
  70. package/tools/inbox_drain.py +249 -0
  71. package/tools/launch_tui.py +51 -3
  72. package/tools/lock.mjs +211 -0
  73. package/tools/metrics_gate.py +205 -0
  74. package/tools/orchestrator_status.py +99 -0
  75. package/tools/power_selftest.py +386 -0
  76. package/tools/prepublish_scan.py +84 -0
  77. package/tools/proposals.mjs +248 -0
  78. package/tools/reconstitute.sh +467 -0
  79. package/tools/rotate_logs.py +228 -0
  80. package/tools/scanner_selftest.py +303 -0
  81. package/tools/secret_scan.py +131 -31
  82. package/tools/stall_check.py +172 -0
  83. package/tools/verify_dash.py +694 -0
  84. package/tools/verify_submit_encoding.py +194 -0
  85. package/ui/CLAUDE.md +109 -0
  86. package/ui/__pycache__/agents.cpython-314.pyc +0 -0
  87. package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
  88. package/ui/__pycache__/config.cpython-314.pyc +0 -0
  89. package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
  90. package/ui/__pycache__/handler.cpython-314.pyc +0 -0
  91. package/ui/__pycache__/render.cpython-314.pyc +0 -0
  92. package/ui/__pycache__/serve.cpython-314.pyc +0 -0
  93. package/ui/__pycache__/sse.cpython-314.pyc +0 -0
  94. package/ui/agents.py +179 -0
  95. package/ui/api/__init__.py +84 -0
  96. package/ui/api/__pycache__/__init__.cpython-314.pyc +0 -0
  97. package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
  98. package/ui/api/__pycache__/tracker.cpython-314.pyc +0 -0
  99. package/ui/api/submit.py +58 -0
  100. package/ui/api/tracker.py +116 -0
  101. package/ui/collectors.py +586 -0
  102. package/ui/config.py +114 -0
  103. package/ui/csrf.py +140 -0
  104. package/ui/handler.py +426 -0
  105. package/ui/render.py +29 -0
  106. package/ui/serve.py +68 -796
  107. package/ui/sse.py +168 -0
  108. package/ui/templates/dashboard.html +1202 -0
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Automated silent-hang detection for agent transcripts.
4
+
5
+ Usage:
6
+ stall_check.py [--transcripts-root DIR] [--threshold-seconds SEC] [--json] [--exit-nonzero-on-stall]
7
+
8
+ Options:
9
+ --transcripts-root DIR Root directory to scan for agent-*.jsonl transcripts.
10
+ Defaults to AESOP_TRANSCRIPTS_ROOT env var, or ~/.claude/projects if unset.
11
+ --threshold-seconds SEC Max age in seconds for a "fresh" transcript (default: 600).
12
+ Transcripts older than this are flagged as stalled.
13
+ --json Output as JSON list of {agent_id, age_seconds, stalled, last_mtime}.
14
+ --exit-nonzero-on-stall Exit 1 if any agent is detected as stalled; default exit 0 always.
15
+
16
+ Behavior:
17
+ - Walks transcripts-root for files matching agent-*.jsonl.
18
+ - For each file, computes age = now - file mtime (seconds).
19
+ - Reports agents as stalled if age > threshold-seconds.
20
+ - Default output: human-readable table; add --json for structured output.
21
+ - Gracefully reports "no transcripts found" if root is missing or empty.
22
+ - Exit code: 0 always (unless --exit-nonzero-on-stall specified and stalls detected).
23
+ """
24
+
25
+ import sys
26
+ import os
27
+ import time
28
+ import json
29
+ import argparse
30
+ from pathlib import Path
31
+
32
+
33
+ def get_transcripts_root():
34
+ """Resolve transcripts root from env var or default to ~/.claude/projects."""
35
+ if os.environ.get("AESOP_TRANSCRIPTS_ROOT"):
36
+ return Path(os.environ["AESOP_TRANSCRIPTS_ROOT"])
37
+ # Default to ~/.claude/projects
38
+ return Path.home() / ".claude" / "projects"
39
+
40
+
41
+ def scan_transcripts(transcripts_root, threshold_seconds):
42
+ """Scan transcripts root for agent-*.jsonl files and compute staleness.
43
+
44
+ Returns: list of dicts {agent, transcript, mtime_age_s, verdict, suggested_action, last_mtime (ISO)}
45
+ """
46
+ transcripts_root = Path(transcripts_root)
47
+
48
+ if not transcripts_root.exists():
49
+ return None # Signal: root missing
50
+
51
+ now = time.time()
52
+ results = []
53
+
54
+ # Thresholds for verdict classification
55
+ STALE_THRESHOLD = threshold_seconds
56
+ DEAD_THRESHOLD = threshold_seconds * 2 # Dead if 2x threshold
57
+
58
+ # Walk all subdirectories for agent-*.jsonl files
59
+ for jsonl_file in transcripts_root.rglob("agent-*.jsonl"):
60
+ if not jsonl_file.is_file():
61
+ continue
62
+
63
+ mtime = jsonl_file.stat().st_mtime
64
+ age_seconds = int(now - mtime)
65
+
66
+ # Extract agent_id from filename (e.g., agent-abc123.jsonl -> abc123)
67
+ agent_id = jsonl_file.stem.replace("agent-", "")
68
+
69
+ # Determine verdict
70
+ if age_seconds <= STALE_THRESHOLD:
71
+ verdict = "ok"
72
+ suggested_action = None
73
+ elif age_seconds <= DEAD_THRESHOLD:
74
+ verdict = "stale"
75
+ suggested_action = "monitor for progress or investigate why transcript is stalled"
76
+ else:
77
+ verdict = "dead"
78
+ suggested_action = "investigate immediately; agent may be hung or crashed"
79
+
80
+ results.append({
81
+ "agent": agent_id,
82
+ "transcript": str(jsonl_file),
83
+ "mtime_age_s": age_seconds,
84
+ "verdict": verdict,
85
+ "suggested_action": suggested_action,
86
+ "last_mtime": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(mtime)),
87
+ })
88
+
89
+ return results
90
+
91
+
92
+ def print_human_table(results):
93
+ """Print results as a human-readable table."""
94
+ if not results:
95
+ print("no transcripts found")
96
+ return
97
+
98
+ # Sort by mtime_age_s (oldest first)
99
+ sorted_results = sorted(results, key=lambda r: r["mtime_age_s"], reverse=True)
100
+
101
+ # Header
102
+ print(f"{'AGENT':<30} {'AGE (s)':<10} {'VERDICT':<10} {'ACTION':<40}")
103
+ print("-" * 90)
104
+
105
+ for entry in sorted_results:
106
+ action = entry["suggested_action"] or "—"
107
+ # Truncate action for display
108
+ action = action[:38] if len(action) > 38 else action
109
+ print(f"{entry['agent']:<30} {entry['mtime_age_s']:<10} {entry['verdict']:<10} {action:<40}")
110
+
111
+
112
+ def print_json_output(results):
113
+ """Print results as JSON."""
114
+ if results is None:
115
+ print(json.dumps([]))
116
+ else:
117
+ print(json.dumps(results, indent=2))
118
+
119
+
120
+ def main():
121
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
122
+ parser.add_argument(
123
+ "--transcripts-root",
124
+ default=None,
125
+ help="Root directory to scan for agent-*.jsonl files (default: AESOP_TRANSCRIPTS_ROOT or ~/.claude/projects)",
126
+ )
127
+ parser.add_argument(
128
+ "--threshold-seconds",
129
+ type=int,
130
+ default=600,
131
+ help="Max age in seconds for a 'fresh' transcript (default: 600)",
132
+ )
133
+ parser.add_argument(
134
+ "--json",
135
+ action="store_true",
136
+ help="Output as JSON instead of human-readable table",
137
+ )
138
+ parser.add_argument(
139
+ "--exit-nonzero-on-stall",
140
+ action="store_true",
141
+ help="Exit 1 if any agent is stalled (default: always exit 0)",
142
+ )
143
+
144
+ args = parser.parse_args()
145
+
146
+ # Resolve transcripts root
147
+ transcripts_root = args.transcripts_root if args.transcripts_root else get_transcripts_root()
148
+
149
+ # Scan transcripts
150
+ results = scan_transcripts(transcripts_root, args.threshold_seconds)
151
+
152
+ # Output
153
+ if args.json:
154
+ print_json_output(results)
155
+ else:
156
+ if results is None:
157
+ print("no transcripts found")
158
+ else:
159
+ print_human_table(results)
160
+
161
+ # Determine exit code
162
+ exit_code = 0
163
+ if args.exit_nonzero_on_stall and results:
164
+ has_stalled = any(r["verdict"] in ("stale", "dead") for r in results)
165
+ if has_stalled:
166
+ exit_code = 1
167
+
168
+ sys.exit(exit_code)
169
+
170
+
171
+ if __name__ == "__main__":
172
+ main()