kin: KIN-083 Healthcheck claude CLI auth: перед запуском pipeline проверять что claude залогинен (быстрый claude -p 'ok' --output-format json, проверить is_error и 'Not logged in'). Если не залогинен — не запускать pipeline, а показать ошибку 'Claude CLI requires login' в GUI с инструкцией.

This commit is contained in:
Gros Frumos 2026-03-16 15:48:09 +02:00
parent a80679ae72
commit bfc8f1c0bb
18 changed files with 1390 additions and 57 deletions

View file

@ -457,13 +457,20 @@ def _seed_default_hooks(conn: sqlite3.Connection):
Creates rebuild-frontend hook only when:
- project 'kin' exists in the projects table
- the hook doesn't already exist (no duplicate)
Also updates existing hooks to the correct command/config if outdated.
"""
kin_exists = conn.execute(
"SELECT 1 FROM projects WHERE id = 'kin'"
kin_row = conn.execute(
"SELECT path FROM projects WHERE id = 'kin'"
).fetchone()
if not kin_exists:
if not kin_row or not kin_row["path"]:
return
_PROJECT_PATH = kin_row["path"].rstrip("/")
_REBUILD_SCRIPT = f"{_PROJECT_PATH}/scripts/rebuild-frontend.sh"
_REBUILD_TRIGGER = "web/frontend/*"
_REBUILD_WORKDIR = _PROJECT_PATH
exists = conn.execute(
"SELECT 1 FROM hooks"
" WHERE project_id = 'kin'"
@ -472,12 +479,25 @@ def _seed_default_hooks(conn: sqlite3.Connection):
).fetchone()
if not exists:
conn.execute(
"""INSERT INTO hooks (project_id, name, event, command, enabled)
"""INSERT INTO hooks
(project_id, name, event, trigger_module_path, command,
working_dir, timeout_seconds, enabled)
VALUES ('kin', 'rebuild-frontend', 'pipeline_completed',
'cd /Users/grosfrumos/projects/kin/web/frontend && npm run build',
1)"""
?, ?, ?, 300, 1)""",
(_REBUILD_TRIGGER, _REBUILD_SCRIPT, _REBUILD_WORKDIR),
)
conn.commit()
else:
# Migrate existing hook: set trigger_module_path, correct command, working_dir
conn.execute(
"""UPDATE hooks
SET trigger_module_path = ?,
command = ?,
working_dir = ?,
timeout_seconds = 300
WHERE project_id = 'kin' AND name = 'rebuild-frontend'""",
(_REBUILD_TRIGGER, _REBUILD_SCRIPT, _REBUILD_WORKDIR),
)
conn.commit()
# Enable autocommit for kin project (opt-in, idempotent)
conn.execute(

View file

@ -115,9 +115,14 @@ def run_hooks(
task_id: str | None,
event: str,
task_modules: list[dict],
changed_files: list[str] | None = None,
) -> list[HookResult]:
"""Run matching hooks for the given event and module list.
If changed_files is provided, trigger_module_path is matched against
the actual git-changed file paths (more precise than task_modules).
Falls back to task_modules matching when changed_files is None.
Never raises hook failures are logged but don't affect the pipeline.
"""
hooks = get_hooks(conn, project_id, event=event)
@ -125,10 +130,13 @@ def run_hooks(
for hook in hooks:
if hook["trigger_module_path"] is not None:
pattern = hook["trigger_module_path"]
matched = any(
fnmatch.fnmatch(m.get("path", ""), pattern)
for m in task_modules
)
if changed_files is not None:
matched = any(fnmatch.fnmatch(f, pattern) for f in changed_files)
else:
matched = any(
fnmatch.fnmatch(m.get("path", ""), pattern)
for m in task_modules
)
if not matched:
continue

View file

@ -99,6 +99,15 @@ def get_project(conn: sqlite3.Connection, id: str) -> dict | None:
return _row_to_dict(row)
def delete_project(conn: sqlite3.Connection, id: str) -> None:
"""Delete a project and all its related data (modules, decisions, tasks)."""
# Delete tables that have FK references to tasks BEFORE deleting tasks
for table in ("modules", "agent_logs", "decisions", "pipelines", "tasks"):
conn.execute(f"DELETE FROM {table} WHERE project_id = ?", (id,))
conn.execute("DELETE FROM projects WHERE id = ?", (id,))
conn.commit()
def get_effective_mode(conn: sqlite3.Connection, project_id: str, task_id: str) -> str:
"""Return effective execution mode: 'auto' or 'review'.
@ -381,17 +390,26 @@ def add_module(
) -> dict:
"""Register a project module."""
cur = conn.execute(
"""INSERT INTO modules (project_id, name, type, path, description,
"""INSERT OR IGNORE INTO modules (project_id, name, type, path, description,
owner_role, dependencies)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(project_id, name, type, path, description, owner_role,
_json_encode(dependencies)),
)
created = cur.rowcount > 0
conn.commit()
row = conn.execute(
"SELECT * FROM modules WHERE id = ?", (cur.lastrowid,)
).fetchone()
return _row_to_dict(row)
if cur.lastrowid:
row = conn.execute(
"SELECT * FROM modules WHERE id = ?", (cur.lastrowid,)
).fetchone()
else:
row = conn.execute(
"SELECT * FROM modules WHERE project_id = ? AND name = ?",
(project_id, name),
).fetchone()
result = _row_to_dict(row)
result["_created"] = created
return result
def get_modules(conn: sqlite3.Connection, project_id: str) -> list[dict]: