@misterhuydo/sentinel 1.4.97 → 1.4.99
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-30T05:
|
|
3
|
-
"checkpoint_at": "2026-03-30T05:
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-30T05:55:15.774Z",
|
|
3
|
+
"checkpoint_at": "2026-03-30T05:55:15.775Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -38,5 +38,29 @@ def ensure_installed() -> bool:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def index_repo(repo: RepoConfig) -> bool:
|
|
41
|
-
"""
|
|
42
|
-
|
|
41
|
+
"""Run `cairn install` in the repo if not already initialised.
|
|
42
|
+
|
|
43
|
+
Cairn indexes automatically via hooks once installed — this just ensures
|
|
44
|
+
the .cairn project file and MCP registration exist before the first fix attempt.
|
|
45
|
+
"""
|
|
46
|
+
import os
|
|
47
|
+
cairn_marker = os.path.join(repo.local_path, ".cairn", ".cairn-project")
|
|
48
|
+
if os.path.exists(cairn_marker):
|
|
49
|
+
logger.debug("cairn already installed in %s", repo.local_path)
|
|
50
|
+
return True
|
|
51
|
+
try:
|
|
52
|
+
r = subprocess.run(
|
|
53
|
+
[CAIRN_BIN, "install"],
|
|
54
|
+
cwd=repo.local_path,
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
timeout=30,
|
|
58
|
+
)
|
|
59
|
+
if r.returncode == 0:
|
|
60
|
+
logger.info("cairn installed in %s", repo.local_path)
|
|
61
|
+
return True
|
|
62
|
+
logger.warning("cairn install failed in %s: %s", repo.local_path, r.stderr.strip())
|
|
63
|
+
return False
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.warning("cairn install error in %s: %s", repo.local_path, e)
|
|
66
|
+
return False
|
|
@@ -57,8 +57,8 @@ class SentinelConfig:
|
|
|
57
57
|
auto_upgrade: bool = True # auto-upgrade on new patch versions
|
|
58
58
|
version_pin: str = "" # if set, never upgrade beyond this version
|
|
59
59
|
upgrade_check_hours: int = 6 # hours between npm upgrade checks
|
|
60
|
-
smtp_from: str = "" # sender address (required for SES; defaults to smtp_user)
|
|
61
|
-
slack_bot_token: str = ""
|
|
60
|
+
smtp_from: str = "" # sender address (required for SES; defaults to smtp_user)
|
|
61
|
+
slack_bot_token: str = ""
|
|
62
62
|
slack_app_token: str = "" # xapp-... (Socket Mode)
|
|
63
63
|
slack_channel: str = "" # optional: restrict to one channel ID or name
|
|
64
64
|
slack_dm_submitter: bool = True # DM the submitter when a fix is applied or blocked
|
|
@@ -66,6 +66,7 @@ class SentinelConfig:
|
|
|
66
66
|
slack_allowed_users: list[str] = field(default_factory=list) # if set, only these Slack user IDs can talk to Boss
|
|
67
67
|
slack_admin_users: list[str] = field(default_factory=list) # subset of allowed users with admin privileges
|
|
68
68
|
project_name: str = "" # optional: friendly name used by Sentinel Boss (e.g. "1881")
|
|
69
|
+
instance_name: str = "Sentinel" # label used in email subjects, e.g. "Sentinel-Personal"
|
|
69
70
|
# Auth strategy:
|
|
70
71
|
# ANTHROPIC_API_KEY — used by Sentinel Boss conversation (structured tools, cheap per-token)
|
|
71
72
|
# Claude Pro / OAuth — used by fix_engine + ask_codebase when CLAUDE_PRO_FOR_TASKS=true
|
|
@@ -191,7 +192,7 @@ class ConfigLoader:
|
|
|
191
192
|
c.auto_upgrade = d.get("AUTO_UPGRADE", "true").lower() != "false"
|
|
192
193
|
c.version_pin = d.get("VERSION_PIN", "")
|
|
193
194
|
c.upgrade_check_hours = int(d.get("UPGRADE_CHECK_HOURS", 6))
|
|
194
|
-
c.smtp_from = d.get("SMTP_FROM", "")
|
|
195
|
+
c.smtp_from = d.get("SMTP_FROM", "")
|
|
195
196
|
c.slack_bot_token = d.get("SLACK_BOT_TOKEN", "")
|
|
196
197
|
c.slack_app_token = d.get("SLACK_APP_TOKEN", "")
|
|
197
198
|
c.slack_channel = d.get("SLACK_CHANNEL", "")
|
|
@@ -200,6 +201,7 @@ class ConfigLoader:
|
|
|
200
201
|
c.slack_allowed_users = _csv(d.get("SLACK_ALLOWED_USERS", ""))
|
|
201
202
|
c.slack_admin_users = _csv(d.get("SLACK_ADMIN_USERS", ""))
|
|
202
203
|
c.project_name = d.get("PROJECT_NAME", "") or Path(self.config_dir).resolve().parent.name
|
|
204
|
+
c.instance_name = d.get("INSTANCE_NAME", "Sentinel") or "Sentinel"
|
|
203
205
|
# If CLAUDE_PRO_FOR_TASKS is explicitly set anywhere, respect it.
|
|
204
206
|
# Otherwise: if the project supplies its own ANTHROPIC_API_KEY, default to False
|
|
205
207
|
# (bill against their key, not the Claude Pro subscription).
|
|
@@ -43,7 +43,7 @@ def send_fix_notification(cfg: SentinelConfig, fix: dict):
|
|
|
43
43
|
auto_publish = fix.get("auto_publish", False)
|
|
44
44
|
source = fix.get("source", "unknown")
|
|
45
45
|
verb = "fix" if auto_publish else "PR"
|
|
46
|
-
subject = f"[
|
|
46
|
+
subject = f"[{cfg.instance_name}] {verb}({source}): {fix.get('message', '')[:80]}"
|
|
47
47
|
|
|
48
48
|
html = _FIX_TEMPLATE.render(
|
|
49
49
|
generated_at=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"),
|
|
@@ -91,7 +91,7 @@ def build_and_send(cfg: SentinelConfig, store: StateStore):
|
|
|
91
91
|
hours=hours, stats=stats, open_prs=open_prs,
|
|
92
92
|
)
|
|
93
93
|
subject = (
|
|
94
|
-
f"[
|
|
94
|
+
f"[{cfg.instance_name}] Health Digest -- "
|
|
95
95
|
f"{stats['applied']} fixed, {stats['failed']} failed, "
|
|
96
96
|
f"{stats['errors']} detected"
|
|
97
97
|
)
|
|
@@ -153,7 +153,7 @@ def send_failure_notification(cfg: SentinelConfig, details: dict):
|
|
|
153
153
|
body = details.get('body', '')[:1000]
|
|
154
154
|
ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
|
|
155
155
|
|
|
156
|
-
subject = f'[
|
|
156
|
+
subject = f'[{cfg.instance_name}] UNRESOLVED ({source}): {message[:80]}'
|
|
157
157
|
|
|
158
158
|
ctx_html = f'<h3>Context</h3><pre>{body}</pre>' if body else ''
|
|
159
159
|
html = (
|
|
@@ -198,7 +198,7 @@ def send_confirmed_notification(cfg: SentinelConfig, fix: dict):
|
|
|
198
198
|
fingerprint = fix.get('fingerprint', '')
|
|
199
199
|
marker = fix.get('sentinel_marker', '')
|
|
200
200
|
ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
|
|
201
|
-
subject = f'[
|
|
201
|
+
subject = f'[{cfg.instance_name}] ✅ Fix confirmed in production: {repo_name} ({fingerprint[:8]})'
|
|
202
202
|
html = (
|
|
203
203
|
'<!DOCTYPE html><html><head><meta charset="utf-8">'
|
|
204
204
|
'<style>'
|
|
@@ -236,7 +236,7 @@ def send_regression_notification(cfg: SentinelConfig, fix: dict, event: dict):
|
|
|
236
236
|
repo_name = fix.get('repo_name', 'unknown')
|
|
237
237
|
fingerprint = fix.get('fingerprint', '')
|
|
238
238
|
ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
|
|
239
|
-
subject = f'[
|
|
239
|
+
subject = f'[{cfg.instance_name}] ⚠ Regression: fix did not resolve issue in {repo_name}'
|
|
240
240
|
html = (
|
|
241
241
|
'<!DOCTYPE html><html><head><meta charset="utf-8">'
|
|
242
242
|
'<style>'
|
|
@@ -342,7 +342,7 @@ def send_startup_notification(cfg: SentinelConfig, results: dict):
|
|
|
342
342
|
html += warn_html
|
|
343
343
|
html += '<hr><small>Sentinel — Autonomous DevOps Agent</small></body></html>'
|
|
344
344
|
|
|
345
|
-
subject = f'[
|
|
345
|
+
subject = f'[{cfg.instance_name}] {status_label} — {ts}'
|
|
346
346
|
_send_email(cfg, subject, html)
|
|
347
347
|
logger.info('Startup notification sent to %d recipient(s)', len(cfg.mails))
|
|
348
348
|
|
|
@@ -351,7 +351,7 @@ def send_upgrade_notification(cfg: SentinelConfig, old_version: str, new_version
|
|
|
351
351
|
if not cfg.mails:
|
|
352
352
|
return
|
|
353
353
|
ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
|
|
354
|
-
subject = f'[
|
|
354
|
+
subject = f'[{cfg.instance_name}] Upgraded {old_version} → {new_version}'
|
|
355
355
|
html = (
|
|
356
356
|
'<!DOCTYPE html><html><head><meta charset="utf-8">'
|
|
357
357
|
'<style>'
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Sentinel workspace config — shared across all projects in this workspace.
|
|
2
2
|
# Per-project sentinel.properties can override any of these.
|
|
3
3
|
|
|
4
|
+
# Label used in email subjects — e.g. "Sentinel-Personal", "Sentinel-Oracle", "Sentinel-1881"
|
|
5
|
+
# Defaults to "Sentinel" if not set.
|
|
6
|
+
# INSTANCE_NAME=Sentinel
|
|
7
|
+
|
|
4
8
|
# Schedule
|
|
5
9
|
POLL_INTERVAL_SECONDS=120
|
|
6
10
|
|