@misterhuydo/sentinel 1.0.43 → 1.0.45
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 +128 -40
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-22T05:33:05.646Z
|
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-
|
|
3
|
-
"checkpoint_at": "2026-03-
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-22T05:36:04.794Z",
|
|
3
|
+
"checkpoint_at": "2026-03-22T05:36:04.795Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/package.json
CHANGED
|
@@ -69,6 +69,9 @@ What you can do (tools available):
|
|
|
69
69
|
11. list_errors — List recent errors from the state store, optionally filtered by repo or source.
|
|
70
70
|
e.g. "show all errors today", "what errors hit elprint this week?"
|
|
71
71
|
|
|
72
|
+
12. pull_repo — Run git pull on one or all managed repos.
|
|
73
|
+
e.g. "pull changes for sentinel-1881", "git pull all repos", "update the code"
|
|
74
|
+
|
|
72
75
|
Tone: direct, professional, like a senior engineer who owns the system.
|
|
73
76
|
Don't pad responses. Don't say "Great question!" or "Certainly!".
|
|
74
77
|
If you don't know something, use a tool to find out before saying you don't know.
|
|
@@ -229,9 +232,84 @@ _TOOLS = [
|
|
|
229
232
|
},
|
|
230
233
|
},
|
|
231
234
|
},
|
|
235
|
+
{
|
|
236
|
+
"name": "pull_repo",
|
|
237
|
+
"description": (
|
|
238
|
+
"Run git pull on one or all managed repos to fetch latest changes from GitHub. "
|
|
239
|
+
"Use for: 'pull changes', 'git pull', 'update repo X', 'fetch latest code'."
|
|
240
|
+
),
|
|
241
|
+
"input_schema": {
|
|
242
|
+
"type": "object",
|
|
243
|
+
"properties": {
|
|
244
|
+
"repo": {
|
|
245
|
+
"type": "string",
|
|
246
|
+
"description": "Repo name to pull (omit to pull all configured repos)",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"name": "pull_config",
|
|
253
|
+
"description": (
|
|
254
|
+
"Run git pull on one or all Sentinel project config directories. "
|
|
255
|
+
"Projects are matched by short name ('1881', 'elprint') or full dir name ('sentinel-1881'). "
|
|
256
|
+
"Use for: 'pull config for 1881', 'update sentinel config', 'pull all configs'."
|
|
257
|
+
),
|
|
258
|
+
"input_schema": {
|
|
259
|
+
"type": "object",
|
|
260
|
+
"properties": {
|
|
261
|
+
"project": {
|
|
262
|
+
"type": "string",
|
|
263
|
+
"description": "Project short name or dir name to pull (omit for all projects)",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
232
268
|
]
|
|
233
269
|
|
|
234
270
|
|
|
271
|
+
# ── Workspace helpers ─────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
def _workspace_dir() -> Path:
|
|
274
|
+
return Path(".").resolve().parent
|
|
275
|
+
|
|
276
|
+
def _short_name(dir_name: str) -> str:
|
|
277
|
+
"""'sentinel-1881' → '1881', 'sentinel-elprint' → 'elprint', others unchanged."""
|
|
278
|
+
if dir_name.startswith("sentinel-"):
|
|
279
|
+
return dir_name[len("sentinel-"):]
|
|
280
|
+
return dir_name
|
|
281
|
+
|
|
282
|
+
def _find_project_dirs(target: str = "") -> list[Path]:
|
|
283
|
+
"""Return project dirs matching target (short or full name), or all if target empty."""
|
|
284
|
+
workspace = _workspace_dir()
|
|
285
|
+
results = []
|
|
286
|
+
try:
|
|
287
|
+
for d in sorted(workspace.iterdir()):
|
|
288
|
+
if not d.is_dir() or d.name in ("code", ".git"):
|
|
289
|
+
continue
|
|
290
|
+
if not (d / "config").exists():
|
|
291
|
+
continue
|
|
292
|
+
if target:
|
|
293
|
+
if target.lower() not in d.name.lower() and target.lower() not in _short_name(d.name).lower():
|
|
294
|
+
continue
|
|
295
|
+
results.append(d)
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
return results
|
|
299
|
+
|
|
300
|
+
def _git_pull(path: Path) -> dict:
|
|
301
|
+
try:
|
|
302
|
+
r = subprocess.run(
|
|
303
|
+
["git", "pull", "--rebase", "origin"],
|
|
304
|
+
cwd=str(path), capture_output=True, text=True, timeout=60,
|
|
305
|
+
)
|
|
306
|
+
last = r.stdout.strip().splitlines()[-1] if r.stdout.strip() else "already up to date"
|
|
307
|
+
return {"status": "ok" if r.returncode == 0 else "error",
|
|
308
|
+
"detail": last if r.returncode == 0 else r.stderr.strip()}
|
|
309
|
+
except Exception as e:
|
|
310
|
+
return {"status": "error", "detail": str(e)}
|
|
311
|
+
|
|
312
|
+
|
|
235
313
|
# ── Tool execution ────────────────────────────────────────────────────────────
|
|
236
314
|
|
|
237
315
|
def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
|
|
@@ -320,27 +398,11 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
|
|
|
320
398
|
return json.dumps({"status": "resumed"})
|
|
321
399
|
|
|
322
400
|
if name == "list_projects":
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
"branch": r.branch,
|
|
329
|
-
"auto_publish": r.auto_publish,
|
|
330
|
-
}
|
|
331
|
-
for r in cfg_loader.repos.values()
|
|
332
|
-
]
|
|
333
|
-
# Scan workspace for sibling project instances
|
|
334
|
-
workspace = Path(".").resolve().parent
|
|
335
|
-
other_projects = []
|
|
336
|
-
try:
|
|
337
|
-
for d in sorted(workspace.iterdir()):
|
|
338
|
-
if not d.is_dir() or d.name in ("code", ".git"):
|
|
339
|
-
continue
|
|
340
|
-
repo_cfg_dir = d / "config" / "repo-configs"
|
|
341
|
-
if not repo_cfg_dir.exists():
|
|
342
|
-
continue
|
|
343
|
-
repos_in_project = []
|
|
401
|
+
projects = []
|
|
402
|
+
for d in _find_project_dirs():
|
|
403
|
+
repo_cfg_dir = d / "config" / "repo-configs"
|
|
404
|
+
repos_in_project = []
|
|
405
|
+
if repo_cfg_dir.exists():
|
|
344
406
|
for p in sorted(repo_cfg_dir.glob("*.properties")):
|
|
345
407
|
if p.name.startswith("_"):
|
|
346
408
|
continue
|
|
@@ -349,25 +411,15 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
|
|
|
349
411
|
if line.startswith("REPO_URL"):
|
|
350
412
|
repo_url = line.split("=", 1)[-1].strip()
|
|
351
413
|
break
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
})
|
|
362
|
-
except Exception as e:
|
|
363
|
-
logger.warning("list_projects workspace scan failed: %s", e)
|
|
364
|
-
return json.dumps({
|
|
365
|
-
"this_instance": {
|
|
366
|
-
"project": Path(".").resolve().name,
|
|
367
|
-
"repos": my_repos,
|
|
368
|
-
},
|
|
369
|
-
"workspace_projects": other_projects,
|
|
370
|
-
})
|
|
414
|
+
repos_in_project.append({"repo": p.stem, "url": repo_url})
|
|
415
|
+
projects.append({
|
|
416
|
+
"project": _short_name(d.name),
|
|
417
|
+
"dir": d.name,
|
|
418
|
+
"running": (d / "sentinel.pid").exists(),
|
|
419
|
+
"this": d.resolve() == Path(".").resolve(),
|
|
420
|
+
"repos": repos_in_project,
|
|
421
|
+
})
|
|
422
|
+
return json.dumps({"projects": projects})
|
|
371
423
|
|
|
372
424
|
if name == "search_logs":
|
|
373
425
|
query = inputs.get("query", "")
|
|
@@ -439,6 +491,42 @@ def _run_tool(name: str, inputs: dict, cfg_loader, store) -> str:
|
|
|
439
491
|
pass
|
|
440
492
|
return json.dumps({"sentinel_commits": results})
|
|
441
493
|
|
|
494
|
+
if name == "pull_repo":
|
|
495
|
+
target = inputs.get("repo", "").lower()
|
|
496
|
+
results = []
|
|
497
|
+
for repo_name, repo in cfg_loader.repos.items():
|
|
498
|
+
if target and target not in repo_name.lower():
|
|
499
|
+
continue
|
|
500
|
+
local = Path(repo.local_path)
|
|
501
|
+
if not local.exists():
|
|
502
|
+
results.append({"repo": repo_name, "status": "error", "detail": "local path not found"})
|
|
503
|
+
continue
|
|
504
|
+
try:
|
|
505
|
+
r = subprocess.run(
|
|
506
|
+
["git", "pull", "--rebase", "origin", repo.branch],
|
|
507
|
+
cwd=str(local), capture_output=True, text=True, timeout=60,
|
|
508
|
+
)
|
|
509
|
+
last_line = r.stdout.strip().splitlines()[-1] if r.stdout.strip() else "already up to date"
|
|
510
|
+
if r.returncode == 0:
|
|
511
|
+
results.append({"repo": repo_name, "status": "ok", "detail": last_line})
|
|
512
|
+
else:
|
|
513
|
+
results.append({"repo": repo_name, "status": "error", "detail": r.stderr.strip()})
|
|
514
|
+
except Exception as e:
|
|
515
|
+
results.append({"repo": repo_name, "status": "error", "detail": str(e)})
|
|
516
|
+
return json.dumps({"results": results})
|
|
517
|
+
|
|
518
|
+
if name == "pull_config":
|
|
519
|
+
target = inputs.get("project", "")
|
|
520
|
+
dirs = _find_project_dirs(target)
|
|
521
|
+
if not dirs:
|
|
522
|
+
return json.dumps({"error": f"No project found matching '{target}'"})
|
|
523
|
+
results = []
|
|
524
|
+
for d in dirs:
|
|
525
|
+
res = _git_pull(d)
|
|
526
|
+
results.append({"project": _short_name(d.name), "dir": d.name, **res})
|
|
527
|
+
logger.info("Boss: pull_config %s → %s", d.name, res["status"])
|
|
528
|
+
return json.dumps({"results": results})
|
|
529
|
+
|
|
442
530
|
return json.dumps({"error": f"unknown tool: {name}"})
|
|
443
531
|
|
|
444
532
|
|