@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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-30T05:44:22.167Z",
3
- "checkpoint_at": "2026-03-30T05:44:22.168Z",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.4.97",
3
+ "version": "1.4.99",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -38,5 +38,29 @@ def ensure_installed() -> bool:
38
38
 
39
39
 
40
40
  def index_repo(repo: RepoConfig) -> bool:
41
- """No-op: Cairn indexes automatically via hooks when Claude Code runs."""
42
- return True
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"[Sentinel] {verb}({source}): {fix.get('message', '')[:80]}"
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"[Sentinel] Health Digest -- "
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'[Sentinel] UNRESOLVED ({source}): {message[:80]}'
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'[Sentinel] ✅ Fix confirmed in production: {repo_name} ({fingerprint[:8]})'
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'[Sentinel] ⚠ Regression: fix did not resolve issue in {repo_name}'
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'[Sentinel] {status_label} — {ts}'
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'[Sentinel] Upgraded {old_version} → {new_version}'
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