@misterhuydo/sentinel 1.0.69 → 1.0.70
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 +53 -39
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:47:05.682Z",
|
|
3
|
+
"checkpoint_at": "2026-03-22T16:47:05.683Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -454,19 +454,19 @@ _TOOLS = [
|
|
|
454
454
|
{
|
|
455
455
|
"name": "ask_codebase",
|
|
456
456
|
"description": (
|
|
457
|
-
"Ask any natural-language question about a managed
|
|
457
|
+
"Ask any natural-language question about a managed codebase. "
|
|
458
|
+
"Accepts a repo name (e.g. 'STS', 'elprint-sales') OR a project name (e.g. '1881', 'elprint') "
|
|
459
|
+
"— if a project name is given and it has multiple repos, all are queried. "
|
|
458
460
|
"Claude Code answers using its full codebase knowledge — no need to specify how. "
|
|
459
|
-
"Use for
|
|
460
|
-
"'
|
|
461
|
-
"'what classes handle auth?', 'any security issues in elprint-sales?', "
|
|
462
|
-
"'summarize the architecture of 1881', 'show me the dependency graph'."
|
|
461
|
+
"Use for: 'what does 1881 do?', 'TODOs in 1881', 'find PIN validation in STS', "
|
|
462
|
+
"'security issues in elprint-sales?', 'summarize the cairn repo'."
|
|
463
463
|
),
|
|
464
464
|
"input_schema": {
|
|
465
465
|
"type": "object",
|
|
466
466
|
"properties": {
|
|
467
467
|
"repo": {
|
|
468
468
|
"type": "string",
|
|
469
|
-
"description": "Repo name (
|
|
469
|
+
"description": "Repo name (e.g. 'STS', 'elprint-sales') OR project name (e.g. '1881', 'elprint') — project name queries all its repos",
|
|
470
470
|
},
|
|
471
471
|
"question": {
|
|
472
472
|
"type": "string",
|
|
@@ -1034,49 +1034,63 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
|
|
|
1034
1034
|
})
|
|
1035
1035
|
|
|
1036
1036
|
if name == "ask_codebase":
|
|
1037
|
-
|
|
1038
|
-
question
|
|
1037
|
+
target = inputs.get("repo", "").lower()
|
|
1038
|
+
question = inputs.get("question", "")
|
|
1039
1039
|
|
|
1040
|
-
|
|
1041
|
-
|
|
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())})
|
|
1040
|
+
# 1. Find repos whose name contains the target (e.g. "STS", "elprint-sales")
|
|
1041
|
+
matched = [(rn, r) for rn, r in cfg_loader.repos.items() if target in rn.lower()]
|
|
1046
1042
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1043
|
+
# 2. No repo match — check if target is a project name → use ALL repos in cfg_loader
|
|
1044
|
+
# (each Sentinel instance is scoped to one project, so all repos belong to it)
|
|
1045
|
+
if not matched:
|
|
1046
|
+
current_project = _read_project_name(Path("."))
|
|
1047
|
+
if target in current_project.lower() or current_project.lower() in target:
|
|
1048
|
+
matched = list(cfg_loader.repos.items())
|
|
1050
1049
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1050
|
+
if not matched:
|
|
1051
|
+
return json.dumps({
|
|
1052
|
+
"error": f"No repo or project found matching '{target}'",
|
|
1053
|
+
"available_repos": list(cfg_loader.repos.keys()),
|
|
1054
|
+
})
|
|
1056
1055
|
|
|
1057
1056
|
cfg = cfg_loader.sentinel
|
|
1058
1057
|
env = os.environ.copy()
|
|
1059
1058
|
if cfg.anthropic_api_key:
|
|
1060
1059
|
env["ANTHROPIC_API_KEY"] = cfg.anthropic_api_key
|
|
1061
1060
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1061
|
+
def _ask_one(repo_name, repo_cfg) -> dict:
|
|
1062
|
+
local_path = Path(repo_cfg.local_path)
|
|
1063
|
+
if not local_path.exists():
|
|
1064
|
+
return {"repo": repo_name, "error": f"not cloned yet at {local_path}"}
|
|
1065
|
+
prompt = (
|
|
1066
|
+
f"You are a code analyst. Answer the following question about the codebase at: {local_path}\n\n"
|
|
1067
|
+
f"Question: {question}\n\n"
|
|
1068
|
+
f"Use whatever tools you need to answer accurately. Be concise and direct. Plain text only."
|
|
1067
1069
|
)
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1070
|
+
try:
|
|
1071
|
+
r = subprocess.run(
|
|
1072
|
+
[cfg.claude_code_bin, "--dangerously-skip-permissions", "--print", prompt],
|
|
1073
|
+
capture_output=True, text=True, timeout=180, env=env,
|
|
1074
|
+
cwd=str(local_path),
|
|
1075
|
+
)
|
|
1076
|
+
output = (r.stdout or "").strip()
|
|
1077
|
+
logger.info("Boss ask_codebase %s rc=%d len=%d", repo_name, r.returncode, len(output))
|
|
1078
|
+
if r.returncode != 0 and not output:
|
|
1079
|
+
return {"repo": repo_name, "error": f"claude --print failed (rc={r.returncode}): {(r.stderr or '')[:200]}"}
|
|
1080
|
+
return {"repo": repo_name, "answer": output[:3000]}
|
|
1081
|
+
except subprocess.TimeoutExpired:
|
|
1082
|
+
return {"repo": repo_name, "error": "timed out after 180s"}
|
|
1083
|
+
except Exception as e:
|
|
1084
|
+
return {"repo": repo_name, "error": str(e)}
|
|
1085
|
+
|
|
1086
|
+
if len(matched) == 1:
|
|
1087
|
+
result = _ask_one(*matched[0])
|
|
1088
|
+
# Unwrap single-repo result for cleaner response
|
|
1089
|
+
return json.dumps(result)
|
|
1090
|
+
|
|
1091
|
+
# Multiple repos — query each and combine
|
|
1092
|
+
results = [_ask_one(rn, r) for rn, r in matched]
|
|
1093
|
+
return json.dumps({"project": target, "repos_queried": len(results), "results": results})
|
|
1080
1094
|
|
|
1081
1095
|
if name == "restart_project":
|
|
1082
1096
|
project_arg = inputs.get("project", "").lower()
|