@misterhuydo/sentinel 1.0.66 → 1.0.68
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/.cairn/.hint-lock +1 -1
- package/.cairn/session.json +2 -2
- package/package.json +1 -1
- package/python/sentinel/sentinel_boss.py +190 -0
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-22T16:24:09.877Z
|
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-22T16:
|
|
3
|
-
"checkpoint_at": "2026-03-22T16:
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-22T16:22:20.785Z",
|
|
3
|
+
"checkpoint_at": "2026-03-22T16:22:20.786Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -100,6 +100,17 @@ What you can do (tools available):
|
|
|
100
100
|
process. Safe to run at any time — no restart if already up to date.
|
|
101
101
|
e.g. "upgrade sentinel", "update sentinel", "upgrade yourself"
|
|
102
102
|
|
|
103
|
+
19. ask_codebase — Ask any natural-language question about a managed repo's codebase.
|
|
104
|
+
Claude Code answers using its full knowledge of the code.
|
|
105
|
+
e.g. "what does the 1881 backend do?", "find PIN validation in elprint",
|
|
106
|
+
"any TODOs in cairn?", "are there security issues in elprint-sales?"
|
|
107
|
+
|
|
108
|
+
20. restart_project — Stop and restart a specific project instance (stop.sh + start.sh).
|
|
109
|
+
e.g. "restart 1881", "reboot elprint", "restart the cairn project"
|
|
110
|
+
|
|
111
|
+
21. tail_log — Fetch the last N lines of a log source live, without a grep filter.
|
|
112
|
+
e.g. "show recent SSOLWA logs", "tail STS", "last 200 lines from 1881 logs"
|
|
113
|
+
|
|
103
114
|
When someone asks what you can do, what you support, what your capabilities are, or how you can help,
|
|
104
115
|
reply with a short summary grouped by category:
|
|
105
116
|
|
|
@@ -111,6 +122,10 @@ reply with a short summary grouped by category:
|
|
|
111
122
|
*Log management*
|
|
112
123
|
• `fetch_logs` — pull fresh logs from servers right now — "fetch logs for SSOLWA"
|
|
113
124
|
• `search_logs` — live SSH grep on production servers — "search logs for illegal PIN in 1881"
|
|
125
|
+
• `tail_log` — last N lines of a log source, no filter — "show recent SSOLWA logs"
|
|
126
|
+
|
|
127
|
+
*Codebase questions*
|
|
128
|
+
• `ask_codebase` — any question about a repo's code — "what does 1881 do?", "find PIN validation", "any TODOs?", "security issues?"
|
|
114
129
|
|
|
115
130
|
*Fix management*
|
|
116
131
|
• `get_fix_details` — full details of a specific fix — "show fix abc123"
|
|
@@ -131,6 +146,9 @@ reply with a short summary grouped by category:
|
|
|
131
146
|
• `unwatch_bot` — stop monitoring a bot — "stop watching @errorbot"
|
|
132
147
|
• `list_watched_bots` — show all bots currently being monitored — "which bots are you watching?"
|
|
133
148
|
|
|
149
|
+
*Project control*
|
|
150
|
+
• `restart_project` — stop + restart a specific project — "restart 1881"
|
|
151
|
+
|
|
134
152
|
*Self-management*
|
|
135
153
|
• `upgrade_sentinel` — git pull + pip install + restart — "upgrade sentinel", "update yourself"
|
|
136
154
|
|
|
@@ -433,6 +451,72 @@ _TOOLS = [
|
|
|
433
451
|
),
|
|
434
452
|
"input_schema": {"type": "object", "properties": {}},
|
|
435
453
|
},
|
|
454
|
+
{
|
|
455
|
+
"name": "ask_codebase",
|
|
456
|
+
"description": (
|
|
457
|
+
"Ask any natural-language question about a managed repo's codebase. "
|
|
458
|
+
"Claude Code answers using its full codebase knowledge — no need to specify how. "
|
|
459
|
+
"Use for any codebase question: 'what does the 1881 backend do?', "
|
|
460
|
+
"'find PIN validation code in elprint', 'are there TODOs in cairn?', "
|
|
461
|
+
"'what classes handle auth?', 'any security issues in elprint-sales?', "
|
|
462
|
+
"'summarize the architecture of 1881', 'show me the dependency graph'."
|
|
463
|
+
),
|
|
464
|
+
"input_schema": {
|
|
465
|
+
"type": "object",
|
|
466
|
+
"properties": {
|
|
467
|
+
"repo": {
|
|
468
|
+
"type": "string",
|
|
469
|
+
"description": "Repo name (partial match, e.g. '1881', 'elprint-sales', 'cairn')",
|
|
470
|
+
},
|
|
471
|
+
"question": {
|
|
472
|
+
"type": "string",
|
|
473
|
+
"description": "Natural language question about the codebase",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
"required": ["repo", "question"],
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
"name": "restart_project",
|
|
481
|
+
"description": (
|
|
482
|
+
"Stop and restart a specific Sentinel project instance (runs stop.sh then start.sh). "
|
|
483
|
+
"Use when: 'restart 1881', 'restart elprint', 'reboot the cairn project'. "
|
|
484
|
+
"Safer than restarting all projects at once."
|
|
485
|
+
),
|
|
486
|
+
"input_schema": {
|
|
487
|
+
"type": "object",
|
|
488
|
+
"properties": {
|
|
489
|
+
"project": {
|
|
490
|
+
"type": "string",
|
|
491
|
+
"description": "Project short name or dir name (e.g. '1881', 'elprint')",
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
"required": ["project"],
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
"name": "tail_log",
|
|
499
|
+
"description": (
|
|
500
|
+
"Fetch the last N lines of a log source's live production logs without any grep filter. "
|
|
501
|
+
"Use when: 'show me recent SSOLWA logs', 'tail STS', 'what's happening in 1881 logs right now', "
|
|
502
|
+
"'show last 100 lines from SSOLWA'. Different from search_logs — no pattern required."
|
|
503
|
+
),
|
|
504
|
+
"input_schema": {
|
|
505
|
+
"type": "object",
|
|
506
|
+
"properties": {
|
|
507
|
+
"source": {
|
|
508
|
+
"type": "string",
|
|
509
|
+
"description": "Log source name (partial match against log-config filenames, e.g. 'SSOLWA', 'STS')",
|
|
510
|
+
},
|
|
511
|
+
"lines": {
|
|
512
|
+
"type": "integer",
|
|
513
|
+
"description": "Number of recent lines to fetch (default 100)",
|
|
514
|
+
"default": 100,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
"required": ["source"],
|
|
518
|
+
},
|
|
519
|
+
},
|
|
436
520
|
]
|
|
437
521
|
|
|
438
522
|
|
|
@@ -949,6 +1033,112 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
949
1033
|
"note": "Upgrade started — pulling latest version via npm and restarting. Give me ~30 seconds then I'll be back.",
|
|
950
1034
|
})
|
|
951
1035
|
|
|
1036
|
+
if name == "ask_codebase":
|
|
1037
|
+
repo_name = inputs.get("repo", "").lower()
|
|
1038
|
+
question = inputs.get("question", "")
|
|
1039
|
+
|
|
1040
|
+
repo_cfg = next(
|
|
1041
|
+
(r for rn, r in cfg_loader.repos.items() if repo_name in rn.lower()),
|
|
1042
|
+
None,
|
|
1043
|
+
)
|
|
1044
|
+
if not repo_cfg:
|
|
1045
|
+
return json.dumps({"error": f"Repo '{repo_name}' not found", "available": list(cfg_loader.repos.keys())})
|
|
1046
|
+
|
|
1047
|
+
local_path = Path(repo_cfg.local_path)
|
|
1048
|
+
if not local_path.exists():
|
|
1049
|
+
return json.dumps({"error": f"Repo not cloned yet at {local_path}. Run pull_repo first."})
|
|
1050
|
+
|
|
1051
|
+
prompt = (
|
|
1052
|
+
f"You are a code analyst. Answer the following question about the codebase at: {local_path}\n\n"
|
|
1053
|
+
f"Question: {question}\n\n"
|
|
1054
|
+
f"Use whatever tools you need to answer accurately. Be concise and direct. Plain text only."
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
cfg = cfg_loader.sentinel
|
|
1058
|
+
env = os.environ.copy()
|
|
1059
|
+
if cfg.anthropic_api_key:
|
|
1060
|
+
env["ANTHROPIC_API_KEY"] = cfg.anthropic_api_key
|
|
1061
|
+
|
|
1062
|
+
try:
|
|
1063
|
+
r = subprocess.run(
|
|
1064
|
+
[cfg.claude_code_bin, "--dangerously-skip-permissions", "--print", prompt],
|
|
1065
|
+
capture_output=True, text=True, timeout=180, env=env,
|
|
1066
|
+
cwd=str(local_path),
|
|
1067
|
+
)
|
|
1068
|
+
output = (r.stdout or "").strip()
|
|
1069
|
+
if r.returncode != 0 and not output:
|
|
1070
|
+
return json.dumps({
|
|
1071
|
+
"error": f"`claude --print` failed (rc={r.returncode})",
|
|
1072
|
+
"stderr": (r.stderr or "").strip()[:300],
|
|
1073
|
+
})
|
|
1074
|
+
logger.info("Boss ask_codebase %s rc=%d len=%d", repo_cfg.repo_name, r.returncode, len(output))
|
|
1075
|
+
return json.dumps({"repo": repo_cfg.repo_name, "answer": output[:4000]})
|
|
1076
|
+
except subprocess.TimeoutExpired:
|
|
1077
|
+
return json.dumps({"error": "Codebase query timed out after 180s"})
|
|
1078
|
+
except Exception as e:
|
|
1079
|
+
return json.dumps({"error": str(e)})
|
|
1080
|
+
|
|
1081
|
+
if name == "restart_project":
|
|
1082
|
+
project_arg = inputs.get("project", "").lower()
|
|
1083
|
+
dirs = _find_project_dirs(project_arg)
|
|
1084
|
+
if not dirs:
|
|
1085
|
+
return json.dumps({"error": f"No project found matching '{project_arg}'"})
|
|
1086
|
+
results = []
|
|
1087
|
+
for d in dirs:
|
|
1088
|
+
stop_sh = d / "stop.sh"
|
|
1089
|
+
start_sh = d / "start.sh"
|
|
1090
|
+
if not stop_sh.exists() or not start_sh.exists():
|
|
1091
|
+
results.append({"project": d.name, "status": "error", "detail": "stop.sh or start.sh not found"})
|
|
1092
|
+
continue
|
|
1093
|
+
try:
|
|
1094
|
+
subprocess.run(["bash", str(stop_sh)], cwd=str(d), timeout=30)
|
|
1095
|
+
subprocess.run(["bash", str(start_sh)], cwd=str(d), timeout=30)
|
|
1096
|
+
results.append({"project": d.name, "status": "restarted"})
|
|
1097
|
+
logger.info("Boss: restarted project %s", d.name)
|
|
1098
|
+
except Exception as e:
|
|
1099
|
+
results.append({"project": d.name, "status": "error", "detail": str(e)})
|
|
1100
|
+
return json.dumps({"results": results})
|
|
1101
|
+
|
|
1102
|
+
if name == "tail_log":
|
|
1103
|
+
source = inputs.get("source", "").lower()
|
|
1104
|
+
lines = int(inputs.get("lines", 100))
|
|
1105
|
+
script = Path(__file__).resolve().parent.parent / "scripts" / "fetch_log.sh"
|
|
1106
|
+
log_cfg_dir = Path("config") / "log-configs"
|
|
1107
|
+
|
|
1108
|
+
if not script.exists():
|
|
1109
|
+
return json.dumps({"error": "fetch_log.sh not found"})
|
|
1110
|
+
if not log_cfg_dir.exists():
|
|
1111
|
+
return json.dumps({"error": "config/log-configs/ not found"})
|
|
1112
|
+
|
|
1113
|
+
props_files = sorted(log_cfg_dir.glob("*.properties"))
|
|
1114
|
+
if source:
|
|
1115
|
+
props_files = [p for p in props_files if source in p.stem.lower()]
|
|
1116
|
+
if not props_files:
|
|
1117
|
+
return json.dumps({"error": f"No log-config found matching '{source}'"})
|
|
1118
|
+
|
|
1119
|
+
results = []
|
|
1120
|
+
for props in props_files:
|
|
1121
|
+
env = os.environ.copy()
|
|
1122
|
+
env["TAIL"] = str(lines)
|
|
1123
|
+
env["GREP_FILTER"] = "" # no filter — show everything
|
|
1124
|
+
try:
|
|
1125
|
+
r = subprocess.run(
|
|
1126
|
+
["bash", str(script), str(props)],
|
|
1127
|
+
capture_output=True, text=True, timeout=60, env=env,
|
|
1128
|
+
)
|
|
1129
|
+
tail_lines = (r.stdout or "").strip().splitlines()[-lines:]
|
|
1130
|
+
results.append({
|
|
1131
|
+
"source": props.stem,
|
|
1132
|
+
"lines": len(tail_lines),
|
|
1133
|
+
"content": "\n".join(tail_lines),
|
|
1134
|
+
})
|
|
1135
|
+
logger.info("Boss tail_log %s rc=%d lines=%d", props.stem, r.returncode, len(tail_lines))
|
|
1136
|
+
except subprocess.TimeoutExpired:
|
|
1137
|
+
results.append({"source": props.stem, "error": "timed out"})
|
|
1138
|
+
except Exception as e:
|
|
1139
|
+
results.append({"source": props.stem, "error": str(e)})
|
|
1140
|
+
return json.dumps({"results": results})
|
|
1141
|
+
|
|
952
1142
|
return json.dumps({"error": f"unknown tool: {name}"})
|
|
953
1143
|
|
|
954
1144
|
|