@misterhuydo/sentinel 1.0.66 → 1.0.67
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/session.json +2 -2
- package/package.json +1 -1
- package/python/sentinel/sentinel_boss.py +235 -0
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:08:22.876Z",
|
|
3
|
+
"checkpoint_at": "2026-03-22T16:08:22.877Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -100,6 +100,18 @@ 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. query_codebase — Use Cairn MCP (via Claude Code) to analyse a managed repo. Operations:
|
|
104
|
+
describe=summarize architecture & purpose, search=find by concept/symbol,
|
|
105
|
+
outline=full structure + issue detection, security=vulnerability scan.
|
|
106
|
+
e.g. "summarize the 1881 repo", "find PIN validation in elprint",
|
|
107
|
+
"what does the cairn backend do?", "scan elprint for security issues"
|
|
108
|
+
|
|
109
|
+
20. restart_project — Stop and restart a specific project instance (stop.sh + start.sh).
|
|
110
|
+
e.g. "restart 1881", "reboot elprint", "restart the cairn project"
|
|
111
|
+
|
|
112
|
+
21. tail_log — Fetch the last N lines of a log source live, without a grep filter.
|
|
113
|
+
e.g. "show recent SSOLWA logs", "tail STS", "last 200 lines from 1881 logs"
|
|
114
|
+
|
|
103
115
|
When someone asks what you can do, what you support, what your capabilities are, or how you can help,
|
|
104
116
|
reply with a short summary grouped by category:
|
|
105
117
|
|
|
@@ -111,6 +123,13 @@ reply with a short summary grouped by category:
|
|
|
111
123
|
*Log management*
|
|
112
124
|
• `fetch_logs` — pull fresh logs from servers right now — "fetch logs for SSOLWA"
|
|
113
125
|
• `search_logs` — live SSH grep on production servers — "search logs for illegal PIN in 1881"
|
|
126
|
+
• `tail_log` — last N lines of a log source, no filter — "show recent SSOLWA logs"
|
|
127
|
+
|
|
128
|
+
*Codebase intelligence (Cairn MCP)*
|
|
129
|
+
• `query_codebase describe` — summarize what a repo does — "what does the 1881 backend do?"
|
|
130
|
+
• `query_codebase search` — find classes/functions by concept — "find PIN validation in elprint"
|
|
131
|
+
• `query_codebase outline` — full structure + issue detection — "outline the cairn repo"
|
|
132
|
+
• `query_codebase security` — vulnerability scan — "scan elprint for security issues"
|
|
114
133
|
|
|
115
134
|
*Fix management*
|
|
116
135
|
• `get_fix_details` — full details of a specific fix — "show fix abc123"
|
|
@@ -131,6 +150,9 @@ reply with a short summary grouped by category:
|
|
|
131
150
|
• `unwatch_bot` — stop monitoring a bot — "stop watching @errorbot"
|
|
132
151
|
• `list_watched_bots` — show all bots currently being monitored — "which bots are you watching?"
|
|
133
152
|
|
|
153
|
+
*Project control*
|
|
154
|
+
• `restart_project` — stop + restart a specific project — "restart 1881"
|
|
155
|
+
|
|
134
156
|
*Self-management*
|
|
135
157
|
• `upgrade_sentinel` — git pull + pip install + restart — "upgrade sentinel", "update yourself"
|
|
136
158
|
|
|
@@ -433,6 +455,80 @@ _TOOLS = [
|
|
|
433
455
|
),
|
|
434
456
|
"input_schema": {"type": "object", "properties": {}},
|
|
435
457
|
},
|
|
458
|
+
{
|
|
459
|
+
"name": "query_codebase",
|
|
460
|
+
"description": (
|
|
461
|
+
"Use Cairn MCP (via Claude Code) to deeply understand a managed repo's codebase. "
|
|
462
|
+
"Operations: 'describe' — summarize what the repo does (architecture, key classes); "
|
|
463
|
+
"'search' — find classes/functions/components by name or concept; "
|
|
464
|
+
"'outline' — full structural overview + heuristic issue detection; "
|
|
465
|
+
"'security' — scan for XSS, SQLi, hardcoded secrets, weak crypto. "
|
|
466
|
+
"Use for: 'what does the 1881 backend do?', 'find the PIN validation code in elprint', "
|
|
467
|
+
"'summarize the cairn repo', 'scan elprint-sales for security issues', "
|
|
468
|
+
"'what classes handle auth in 1881?'"
|
|
469
|
+
),
|
|
470
|
+
"input_schema": {
|
|
471
|
+
"type": "object",
|
|
472
|
+
"properties": {
|
|
473
|
+
"repo": {
|
|
474
|
+
"type": "string",
|
|
475
|
+
"description": "Repo name (partial match, e.g. '1881', 'elprint-sales')",
|
|
476
|
+
},
|
|
477
|
+
"operation": {
|
|
478
|
+
"type": "string",
|
|
479
|
+
"enum": ["describe", "search", "outline", "security"],
|
|
480
|
+
"description": "describe=summarize, search=find by concept, outline=structure+issues, security=vuln scan",
|
|
481
|
+
"default": "describe",
|
|
482
|
+
},
|
|
483
|
+
"query": {
|
|
484
|
+
"type": "string",
|
|
485
|
+
"description": "For 'search': the concept or symbol to look for (e.g. 'PIN validation', 'AuthService')",
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
"required": ["repo"],
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"name": "restart_project",
|
|
493
|
+
"description": (
|
|
494
|
+
"Stop and restart a specific Sentinel project instance (runs stop.sh then start.sh). "
|
|
495
|
+
"Use when: 'restart 1881', 'restart elprint', 'reboot the cairn project'. "
|
|
496
|
+
"Safer than restarting all projects at once."
|
|
497
|
+
),
|
|
498
|
+
"input_schema": {
|
|
499
|
+
"type": "object",
|
|
500
|
+
"properties": {
|
|
501
|
+
"project": {
|
|
502
|
+
"type": "string",
|
|
503
|
+
"description": "Project short name or dir name (e.g. '1881', 'elprint')",
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
"required": ["project"],
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"name": "tail_log",
|
|
511
|
+
"description": (
|
|
512
|
+
"Fetch the last N lines of a log source's live production logs without any grep filter. "
|
|
513
|
+
"Use when: 'show me recent SSOLWA logs', 'tail STS', 'what's happening in 1881 logs right now', "
|
|
514
|
+
"'show last 100 lines from SSOLWA'. Different from search_logs — no pattern required."
|
|
515
|
+
),
|
|
516
|
+
"input_schema": {
|
|
517
|
+
"type": "object",
|
|
518
|
+
"properties": {
|
|
519
|
+
"source": {
|
|
520
|
+
"type": "string",
|
|
521
|
+
"description": "Log source name (partial match against log-config filenames, e.g. 'SSOLWA', 'STS')",
|
|
522
|
+
},
|
|
523
|
+
"lines": {
|
|
524
|
+
"type": "integer",
|
|
525
|
+
"description": "Number of recent lines to fetch (default 100)",
|
|
526
|
+
"default": 100,
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
"required": ["source"],
|
|
530
|
+
},
|
|
531
|
+
},
|
|
436
532
|
]
|
|
437
533
|
|
|
438
534
|
|
|
@@ -949,6 +1045,145 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
949
1045
|
"note": "Upgrade started — pulling latest version via npm and restarting. Give me ~30 seconds then I'll be back.",
|
|
950
1046
|
})
|
|
951
1047
|
|
|
1048
|
+
if name == "query_codebase":
|
|
1049
|
+
repo_name = inputs.get("repo", "").lower()
|
|
1050
|
+
operation = inputs.get("operation", "describe")
|
|
1051
|
+
query = inputs.get("query", "")
|
|
1052
|
+
|
|
1053
|
+
# Match repo by name
|
|
1054
|
+
repo_cfg = next(
|
|
1055
|
+
(r for rn, r in cfg_loader.repos.items() if repo_name in rn.lower()),
|
|
1056
|
+
None,
|
|
1057
|
+
)
|
|
1058
|
+
if not repo_cfg:
|
|
1059
|
+
available = list(cfg_loader.repos.keys())
|
|
1060
|
+
return json.dumps({"error": f"Repo '{repo_name}' not found", "available": available})
|
|
1061
|
+
|
|
1062
|
+
local_path = Path(repo_cfg.local_path)
|
|
1063
|
+
if not local_path.exists():
|
|
1064
|
+
return json.dumps({"error": f"Repo not cloned yet at {local_path}. Run pull_repo first."})
|
|
1065
|
+
|
|
1066
|
+
# Build a targeted prompt for Claude Code + Cairn MCP
|
|
1067
|
+
if operation == "describe":
|
|
1068
|
+
prompt = (
|
|
1069
|
+
f"You are analysing the codebase at: {local_path}\n"
|
|
1070
|
+
f"Use the cairn_describe MCP tool to summarise what this project does — "
|
|
1071
|
+
f"its purpose, main architecture, key classes/services, and tech stack. "
|
|
1072
|
+
f"Be concise but informative. Output plain text only."
|
|
1073
|
+
)
|
|
1074
|
+
elif operation == "search":
|
|
1075
|
+
if not query:
|
|
1076
|
+
return json.dumps({"error": "query is required for operation=search"})
|
|
1077
|
+
prompt = (
|
|
1078
|
+
f"You are analysing the codebase at: {local_path}\n"
|
|
1079
|
+
f"Use the cairn_search MCP tool to find '{query}'. "
|
|
1080
|
+
f"List matching classes, functions, files, and a brief description of each. "
|
|
1081
|
+
f"Output plain text only."
|
|
1082
|
+
)
|
|
1083
|
+
elif operation == "outline":
|
|
1084
|
+
prompt = (
|
|
1085
|
+
f"You are analysing the codebase at: {local_path}\n"
|
|
1086
|
+
f"Use the cairn_outline MCP tool to give a full structural overview. "
|
|
1087
|
+
f"Include key modules, packages, detected issues (god classes, gaps, etc.). "
|
|
1088
|
+
f"Output plain text only."
|
|
1089
|
+
)
|
|
1090
|
+
elif operation == "security":
|
|
1091
|
+
prompt = (
|
|
1092
|
+
f"You are analysing the codebase at: {local_path}\n"
|
|
1093
|
+
f"Use the cairn_security MCP tool to scan for vulnerabilities. "
|
|
1094
|
+
f"List findings grouped by severity. Output plain text only."
|
|
1095
|
+
)
|
|
1096
|
+
else:
|
|
1097
|
+
return json.dumps({"error": f"Unknown operation '{operation}'"})
|
|
1098
|
+
|
|
1099
|
+
cfg = cfg_loader.sentinel
|
|
1100
|
+
env = os.environ.copy()
|
|
1101
|
+
if cfg.anthropic_api_key:
|
|
1102
|
+
env["ANTHROPIC_API_KEY"] = cfg.anthropic_api_key
|
|
1103
|
+
|
|
1104
|
+
try:
|
|
1105
|
+
r = subprocess.run(
|
|
1106
|
+
[cfg.claude_code_bin, "--dangerously-skip-permissions", "--print", prompt],
|
|
1107
|
+
capture_output=True, text=True, timeout=180, env=env,
|
|
1108
|
+
cwd=str(local_path),
|
|
1109
|
+
)
|
|
1110
|
+
output = (r.stdout or "").strip()
|
|
1111
|
+
if r.returncode != 0 and not output:
|
|
1112
|
+
return json.dumps({
|
|
1113
|
+
"error": f"`claude --print` failed (rc={r.returncode})",
|
|
1114
|
+
"stderr": (r.stderr or "").strip()[:300],
|
|
1115
|
+
})
|
|
1116
|
+
return json.dumps({
|
|
1117
|
+
"repo": repo_cfg.repo_name,
|
|
1118
|
+
"operation": operation,
|
|
1119
|
+
"result": output[:4000],
|
|
1120
|
+
})
|
|
1121
|
+
except subprocess.TimeoutExpired:
|
|
1122
|
+
return json.dumps({"error": "Cairn query timed out after 180s"})
|
|
1123
|
+
except Exception as e:
|
|
1124
|
+
return json.dumps({"error": str(e)})
|
|
1125
|
+
|
|
1126
|
+
if name == "restart_project":
|
|
1127
|
+
project_arg = inputs.get("project", "").lower()
|
|
1128
|
+
dirs = _find_project_dirs(project_arg)
|
|
1129
|
+
if not dirs:
|
|
1130
|
+
return json.dumps({"error": f"No project found matching '{project_arg}'"})
|
|
1131
|
+
results = []
|
|
1132
|
+
for d in dirs:
|
|
1133
|
+
stop_sh = d / "stop.sh"
|
|
1134
|
+
start_sh = d / "start.sh"
|
|
1135
|
+
if not stop_sh.exists() or not start_sh.exists():
|
|
1136
|
+
results.append({"project": d.name, "status": "error", "detail": "stop.sh or start.sh not found"})
|
|
1137
|
+
continue
|
|
1138
|
+
try:
|
|
1139
|
+
subprocess.run(["bash", str(stop_sh)], cwd=str(d), timeout=30)
|
|
1140
|
+
subprocess.run(["bash", str(start_sh)], cwd=str(d), timeout=30)
|
|
1141
|
+
results.append({"project": d.name, "status": "restarted"})
|
|
1142
|
+
logger.info("Boss: restarted project %s", d.name)
|
|
1143
|
+
except Exception as e:
|
|
1144
|
+
results.append({"project": d.name, "status": "error", "detail": str(e)})
|
|
1145
|
+
return json.dumps({"results": results})
|
|
1146
|
+
|
|
1147
|
+
if name == "tail_log":
|
|
1148
|
+
source = inputs.get("source", "").lower()
|
|
1149
|
+
lines = int(inputs.get("lines", 100))
|
|
1150
|
+
script = Path(__file__).resolve().parent.parent / "scripts" / "fetch_log.sh"
|
|
1151
|
+
log_cfg_dir = Path("config") / "log-configs"
|
|
1152
|
+
|
|
1153
|
+
if not script.exists():
|
|
1154
|
+
return json.dumps({"error": "fetch_log.sh not found"})
|
|
1155
|
+
if not log_cfg_dir.exists():
|
|
1156
|
+
return json.dumps({"error": "config/log-configs/ not found"})
|
|
1157
|
+
|
|
1158
|
+
props_files = sorted(log_cfg_dir.glob("*.properties"))
|
|
1159
|
+
if source:
|
|
1160
|
+
props_files = [p for p in props_files if source in p.stem.lower()]
|
|
1161
|
+
if not props_files:
|
|
1162
|
+
return json.dumps({"error": f"No log-config found matching '{source}'"})
|
|
1163
|
+
|
|
1164
|
+
results = []
|
|
1165
|
+
for props in props_files:
|
|
1166
|
+
env = os.environ.copy()
|
|
1167
|
+
env["TAIL"] = str(lines)
|
|
1168
|
+
env["GREP_FILTER"] = "" # no filter — show everything
|
|
1169
|
+
try:
|
|
1170
|
+
r = subprocess.run(
|
|
1171
|
+
["bash", str(script), str(props)],
|
|
1172
|
+
capture_output=True, text=True, timeout=60, env=env,
|
|
1173
|
+
)
|
|
1174
|
+
tail_lines = (r.stdout or "").strip().splitlines()[-lines:]
|
|
1175
|
+
results.append({
|
|
1176
|
+
"source": props.stem,
|
|
1177
|
+
"lines": len(tail_lines),
|
|
1178
|
+
"content": "\n".join(tail_lines),
|
|
1179
|
+
})
|
|
1180
|
+
logger.info("Boss tail_log %s rc=%d lines=%d", props.stem, r.returncode, len(tail_lines))
|
|
1181
|
+
except subprocess.TimeoutExpired:
|
|
1182
|
+
results.append({"source": props.stem, "error": "timed out"})
|
|
1183
|
+
except Exception as e:
|
|
1184
|
+
results.append({"source": props.stem, "error": str(e)})
|
|
1185
|
+
return json.dumps({"results": results})
|
|
1186
|
+
|
|
952
1187
|
return json.dumps({"error": f"unknown tool: {name}"})
|
|
953
1188
|
|
|
954
1189
|
|