Merge pull request #520 from AIOSAI/work/system

feat(system): fix(system): ruff format 13 hook/init files + pip-audit CVE-2026-6357 ignore — unblock CI lint and security
This commit is contained in:
AIPass
2026-05-07 10:22:45 -07:00
committed by GitHub
14 changed files with 107 additions and 157 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- run: pip install pip-audit
- run: pip install -e .
- name: Pip audit
run: pip-audit --skip-editable --ignore-vuln CVE-2026-3219
run: pip-audit --skip-editable --ignore-vuln CVE-2026-3219 --ignore-vuln CVE-2026-6357
codeql:
runs-on: ubuntu-latest
@@ -37,35 +37,32 @@ SKIP_EXTENSIONS = {".md", ".txt", ".log", ".csv", ".html"}
# AIPass-specific Python patterns to check
PYTHON_PATTERNS = {
"bad_optional": {
"pattern": ": str = None",
"message": "Optional param should use 'str | None = None' pattern"
},
"bad_optional": {"pattern": ": str = None", "message": "Optional param should use 'str | None = None' pattern"},
"logger_debug": {
"pattern": "logger.debug(",
"message": "Use logger.info for SystemLogger (logger.debug not supported)"
"message": "Use logger.info for SystemLogger (logger.debug not supported)",
},
"return_error_msg": {
"pattern": "return error_msg",
"message": "Return None for error states, not error_msg string"
"message": "Return None for error states, not error_msg string",
},
"open_no_encoding": {
"pattern": "open(",
"requires_missing": "encoding=",
"message": "open() without encoding='utf-8'"
"message": "open() without encoding='utf-8'",
},
"log_not_log_operation": {
"pattern": ".log(",
"message": "Use log_operation() with success/error params, not .log()"
"message": "Use log_operation() with success/error params, not .log()",
},
"dict_none_no_check": {
"pattern": "Dict | None",
"message": "Dict | None return: Add None check before using (if result is None: return)"
}
"message": "Dict | None return: Add None check before using (if result is None: return)",
},
}
# JSON-specific patterns for emoji corruption
JSON_CORRUPTION_CHARS = ['\ufffd', '\x00']
JSON_CORRUPTION_CHARS = ["\ufffd", "\x00"]
def run_python_checks(file_path: str) -> list[str]:
@@ -75,10 +72,7 @@ def run_python_checks(file_path: str) -> list[str]:
# 1. Syntax check with py_compile
try:
result = subprocess.run(
[sys.executable, "-m", "py_compile", file_path],
capture_output=True,
text=True,
timeout=5
[sys.executable, "-m", "py_compile", file_path], capture_output=True, text=True, timeout=5
)
if result.returncode != 0:
errors.append(f"SYNTAX: {result.stderr.strip()}")
@@ -91,7 +85,7 @@ def run_python_checks(file_path: str) -> list[str]:
["ruff", "check", "--select=E,F,W", "--output-format=text", file_path],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
if result.stdout.strip():
for line in result.stdout.strip().split("\n")[:5]:
@@ -103,12 +97,7 @@ def run_python_checks(file_path: str) -> list[str]:
# 3. Ruff format check — detect format drift
try:
result = subprocess.run(
["ruff", "format", "--check", file_path],
capture_output=True,
text=True,
timeout=10
)
result = subprocess.run(["ruff", "format", "--check", file_path], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
errors.append(f"FORMAT: {Path(file_path).name} needs ruff format (run: ruff format {Path(file_path).name})")
except FileNotFoundError:
@@ -146,19 +135,20 @@ def run_python_checks(file_path: str) -> list[str]:
return errors
def run_ruff_lint_structured(file_path: str) -> list[dict]:
"""Run ruff check and return structured violations for the state file.
Returns list of {line, message} dicts — same format as pyright errors.
Only non-empty when ruff finds real violations (not format drift).
"""
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
result = subprocess.run(
["ruff", "check", "--select=E,F,W", "--output-format=json", file_path],
capture_output=True, text=True, timeout=10
capture_output=True,
text=True,
timeout=10,
)
if not result.stdout.strip():
return []
@@ -179,15 +169,12 @@ def run_ruff_lint_structured(file_path: str) -> list[dict]:
def run_pyright_check(file_path: str) -> list[dict]:
"""Run pyright on a single file. Returns list of error dicts."""
# Skip hook files - they don't follow project standards
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
result = subprocess.run(
[sys.executable, "-m", "pyright", "--outputjson", file_path],
capture_output=True,
text=True,
timeout=15
[sys.executable, "-m", "pyright", "--outputjson", file_path], capture_output=True, text=True, timeout=15
)
try:
@@ -201,10 +188,7 @@ def run_pyright_check(file_path: str) -> list[dict]:
if severity == "error":
line = diag.get("range", {}).get("start", {}).get("line", 0)
message = diag.get("message", "Unknown error")
errors.append({
"line": line,
"message": message[:100]
})
errors.append({"line": line, "message": message[:100]})
return errors[:10] # Max 10 errors
@@ -220,10 +204,7 @@ def save_diagnostics_state(file_path: str, errors: list[dict]):
"""Save type errors to state file for PreToolUse gate."""
try:
if errors:
state = {
"file": str(Path(file_path).resolve()),
"errors": errors
}
state = {"file": str(Path(file_path).resolve()), "errors": errors}
STATE_FILE.write_text(json.dumps(state), encoding="utf-8")
else:
# No errors — clear the state
@@ -249,11 +230,11 @@ def run_json_checks(file_path: str) -> list[str]:
data = json.loads(content)
if isinstance(data, dict):
for key in ['allowed_emojis', 'emojis', 'emoji_list']:
for key in ["allowed_emojis", "emojis", "emoji_list"]:
if key in data and isinstance(data[key], list):
for item in data[key]:
if isinstance(item, str) and len(item) == 1:
if ord(item) < 128 and item not in '\u2713\u2717':
if ord(item) < 128 and item not in "\u2713\u2717":
errors.append(f"EMOJI CORRUPTION: Suspicious char '{item}' in {key}")
break
@@ -268,7 +249,7 @@ def run_json_checks(file_path: str) -> list[str]:
def run_seedgo_checklist(file_path: str) -> list[str]:
"""Run seedgo standards checklist — returns violations only."""
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
@@ -277,7 +258,7 @@ def run_seedgo_checklist(file_path: str) -> list[str]:
capture_output=True,
text=True,
timeout=15,
cwd=str(Path.home() / "Projects" / "AIPass")
cwd=str(Path.home() / "Projects" / "AIPass"),
)
if result.returncode != 0:
@@ -369,17 +350,12 @@ def main():
Fix these errors in {Path(file_path).name} now. Do not skip or defer."""
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": context
},
"systemMessage": f"[AUTO-FIX] {len(errors)} error(s) — fix before continuing"
"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": context},
"systemMessage": f"[AUTO-FIX] {len(errors)} error(s) — fix before continuing",
}
print(json.dumps(output))
else:
output = {
"systemMessage": "[diagnostics] ok"
}
output = {"systemMessage": "[diagnostics] ok"}
print(json.dumps(output))
except Exception:
@@ -8,6 +8,7 @@ AI sees branch-specific context.
Version: 1.0.0
"""
from pathlib import Path
@@ -7,6 +7,7 @@ a notification if any exist.
Version: 1.0.0
"""
import json
from pathlib import Path
@@ -86,7 +87,9 @@ def main():
new_count = count_new_emails(branch_root)
if new_count > 0:
plural = "s" if new_count != 1 else ""
print(f"You have {new_count} new email{plural} - check with: drone @ai_mail inbox | then: drone @ai_mail view <id> | close with: drone @ai_mail close <id>")
print(
f"You have {new_count} new email{plural} - check with: drone @ai_mail inbox | then: drone @ai_mail view <id> | close with: drone @ai_mail close <id>"
)
if __name__ == "__main__":
@@ -7,6 +7,7 @@ Finds the branch root by walking up from CWD looking for apps/ or *.id.json.
Version: 1.0.0
"""
import json
from pathlib import Path
+10 -3
View File
@@ -71,6 +71,7 @@ def _get_local_json_path() -> Path:
"""Always resolve .trinity/local.json from CWD (user's project)."""
return Path.cwd() / ".trinity" / "local.json"
CLI_CHOICES = ["claude", "codex", "gemini", "other"]
# Flag variants for CLI launch — these are user-facing config values, not code-level flags
FLAG_CHOICES = ["default", "skip-permissions"]
@@ -206,7 +207,9 @@ def stage_1_welcome(dry_run: bool = False) -> Dict[str, Any]:
console.print("Let's walk through setup together. This takes about 5 minutes.")
if shutil.which("drone"):
console.print()
console.print("[dim]Tip: Open another terminal and run [cyan]drone @prax monitor run[/cyan] to watch activity live.[/dim]")
console.print(
"[dim]Tip: Open another terminal and run [cyan]drone @prax monitor run[/cyan] to watch activity live.[/dim]"
)
if dry_run:
console.print("[yellow]\\[dry-run][/yellow] No state will be written, no subprocesses launched.")
console.print()
@@ -476,12 +479,16 @@ def stage_9_ping_sweep(non_interactive: bool = False, dry_run: bool = False) ->
aipass_registry = _BRANCH_ROOT.parent / "AIPASS_REGISTRY.json"
if not aipass_registry.exists():
console.print(f"[dim] Found {len(branches)} agent(s) in this project.[/dim]")
console.print("[dim] Ping requires a running session — your agents will be reachable after handoff (next step).[/dim]")
console.print(
"[dim] Ping requires a running session — your agents will be reachable after handoff (next step).[/dim]"
)
_save_stage(9, {"results": {}, "skipped_standalone": True}, dry_run=dry_run)
return {"ping_results": {}}
console.print(f"[dim] Found {len(branches)} agent(s). Checking reachability...[/dim]")
console.print("[dim] (Agents with a running session will auto-ack; new agents will time out — that's normal.)[/dim]")
console.print(
"[dim] (Agents with a running session will auto-ack; new agents will time out — that's normal.)[/dim]"
)
results = ping_sweep.sweep_all_branches(timeout=10)
@@ -37,35 +37,32 @@ SKIP_EXTENSIONS = {".md", ".txt", ".log", ".csv", ".html"}
# AIPass-specific Python patterns to check
PYTHON_PATTERNS = {
"bad_optional": {
"pattern": ": str = None",
"message": "Optional param should use 'str | None = None' pattern"
},
"bad_optional": {"pattern": ": str = None", "message": "Optional param should use 'str | None = None' pattern"},
"logger_debug": {
"pattern": "logger.debug(",
"message": "Use logger.info for SystemLogger (logger.debug not supported)"
"message": "Use logger.info for SystemLogger (logger.debug not supported)",
},
"return_error_msg": {
"pattern": "return error_msg",
"message": "Return None for error states, not error_msg string"
"message": "Return None for error states, not error_msg string",
},
"open_no_encoding": {
"pattern": "open(",
"requires_missing": "encoding=",
"message": "open() without encoding='utf-8'"
"message": "open() without encoding='utf-8'",
},
"log_not_log_operation": {
"pattern": ".log(",
"message": "Use log_operation() with success/error params, not .log()"
"message": "Use log_operation() with success/error params, not .log()",
},
"dict_none_no_check": {
"pattern": "Dict | None",
"message": "Dict | None return: Add None check before using (if result is None: return)"
}
"message": "Dict | None return: Add None check before using (if result is None: return)",
},
}
# JSON-specific patterns for emoji corruption
JSON_CORRUPTION_CHARS = ['\ufffd', '\x00']
JSON_CORRUPTION_CHARS = ["\ufffd", "\x00"]
def run_python_checks(file_path: str) -> list[str]:
@@ -75,10 +72,7 @@ def run_python_checks(file_path: str) -> list[str]:
# 1. Syntax check with py_compile
try:
result = subprocess.run(
[sys.executable, "-m", "py_compile", file_path],
capture_output=True,
text=True,
timeout=5
[sys.executable, "-m", "py_compile", file_path], capture_output=True, text=True, timeout=5
)
if result.returncode != 0:
errors.append(f"SYNTAX: {result.stderr.strip()}")
@@ -91,7 +85,7 @@ def run_python_checks(file_path: str) -> list[str]:
["ruff", "check", "--select=E,F,W", "--output-format=text", file_path],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
if result.stdout.strip():
for line in result.stdout.strip().split("\n")[:5]:
@@ -103,12 +97,7 @@ def run_python_checks(file_path: str) -> list[str]:
# 3. Ruff format check — detect format drift
try:
result = subprocess.run(
["ruff", "format", "--check", file_path],
capture_output=True,
text=True,
timeout=10
)
result = subprocess.run(["ruff", "format", "--check", file_path], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
errors.append(f"FORMAT: {Path(file_path).name} needs ruff format (run: ruff format {Path(file_path).name})")
except FileNotFoundError:
@@ -146,19 +135,20 @@ def run_python_checks(file_path: str) -> list[str]:
return errors
def run_ruff_lint_structured(file_path: str) -> list[dict]:
"""Run ruff check and return structured violations for the state file.
Returns list of {line, message} dicts — same format as pyright errors.
Only non-empty when ruff finds real violations (not format drift).
"""
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
result = subprocess.run(
["ruff", "check", "--select=E,F,W", "--output-format=json", file_path],
capture_output=True, text=True, timeout=10
capture_output=True,
text=True,
timeout=10,
)
if not result.stdout.strip():
return []
@@ -179,15 +169,12 @@ def run_ruff_lint_structured(file_path: str) -> list[dict]:
def run_pyright_check(file_path: str) -> list[dict]:
"""Run pyright on a single file. Returns list of error dicts."""
# Skip hook files - they don't follow project standards
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
result = subprocess.run(
[sys.executable, "-m", "pyright", "--outputjson", file_path],
capture_output=True,
text=True,
timeout=15
[sys.executable, "-m", "pyright", "--outputjson", file_path], capture_output=True, text=True, timeout=15
)
try:
@@ -201,10 +188,7 @@ def run_pyright_check(file_path: str) -> list[dict]:
if severity == "error":
line = diag.get("range", {}).get("start", {}).get("line", 0)
message = diag.get("message", "Unknown error")
errors.append({
"line": line,
"message": message[:100]
})
errors.append({"line": line, "message": message[:100]})
return errors[:10] # Max 10 errors
@@ -220,10 +204,7 @@ def save_diagnostics_state(file_path: str, errors: list[dict]):
"""Save type errors to state file for PreToolUse gate."""
try:
if errors:
state = {
"file": str(Path(file_path).resolve()),
"errors": errors
}
state = {"file": str(Path(file_path).resolve()), "errors": errors}
STATE_FILE.write_text(json.dumps(state), encoding="utf-8")
else:
# No errors — clear the state
@@ -249,11 +230,11 @@ def run_json_checks(file_path: str) -> list[str]:
data = json.loads(content)
if isinstance(data, dict):
for key in ['allowed_emojis', 'emojis', 'emoji_list']:
for key in ["allowed_emojis", "emojis", "emoji_list"]:
if key in data and isinstance(data[key], list):
for item in data[key]:
if isinstance(item, str) and len(item) == 1:
if ord(item) < 128 and item not in '\u2713\u2717':
if ord(item) < 128 and item not in "\u2713\u2717":
errors.append(f"EMOJI CORRUPTION: Suspicious char '{item}' in {key}")
break
@@ -268,7 +249,7 @@ def run_json_checks(file_path: str) -> list[str]:
def run_seedgo_checklist(file_path: str) -> list[str]:
"""Run seedgo standards checklist — returns violations only."""
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
@@ -277,7 +258,7 @@ def run_seedgo_checklist(file_path: str) -> list[str]:
capture_output=True,
text=True,
timeout=15,
cwd=str(Path.home() / "Projects" / "AIPass")
cwd=str(Path.home() / "Projects" / "AIPass"),
)
if result.returncode != 0:
@@ -369,17 +350,12 @@ def main():
Fix these errors in {Path(file_path).name} now. Do not skip or defer."""
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": context
},
"systemMessage": f"[AUTO-FIX] {len(errors)} error(s) — fix before continuing"
"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": context},
"systemMessage": f"[AUTO-FIX] {len(errors)} error(s) — fix before continuing",
}
print(json.dumps(output))
else:
output = {
"systemMessage": "[diagnostics] ok"
}
output = {"systemMessage": "[diagnostics] ok"}
print(json.dumps(output))
except Exception:
@@ -8,6 +8,7 @@ AI sees branch-specific context.
Version: 1.0.0
"""
from pathlib import Path
@@ -7,6 +7,7 @@ a notification if any exist.
Version: 1.0.0
"""
import json
from pathlib import Path
@@ -86,7 +87,9 @@ def main():
new_count = count_new_emails(branch_root)
if new_count > 0:
plural = "s" if new_count != 1 else ""
print(f"You have {new_count} new email{plural} - check with: drone @ai_mail inbox | then: drone @ai_mail view <id> | close with: drone @ai_mail close <id>")
print(
f"You have {new_count} new email{plural} - check with: drone @ai_mail inbox | then: drone @ai_mail view <id> | close with: drone @ai_mail close <id>"
)
if __name__ == "__main__":
@@ -7,6 +7,7 @@ Finds the branch root by walking up from CWD looking for apps/ or *.id.json.
Version: 1.0.0
"""
import json
from pathlib import Path
@@ -37,35 +37,32 @@ SKIP_EXTENSIONS = {".md", ".txt", ".log", ".csv", ".html"}
# AIPass-specific Python patterns to check
PYTHON_PATTERNS = {
"bad_optional": {
"pattern": ": str = None",
"message": "Optional param should use 'str | None = None' pattern"
},
"bad_optional": {"pattern": ": str = None", "message": "Optional param should use 'str | None = None' pattern"},
"logger_debug": {
"pattern": "logger.debug(",
"message": "Use logger.info for SystemLogger (logger.debug not supported)"
"message": "Use logger.info for SystemLogger (logger.debug not supported)",
},
"return_error_msg": {
"pattern": "return error_msg",
"message": "Return None for error states, not error_msg string"
"message": "Return None for error states, not error_msg string",
},
"open_no_encoding": {
"pattern": "open(",
"requires_missing": "encoding=",
"message": "open() without encoding='utf-8'"
"message": "open() without encoding='utf-8'",
},
"log_not_log_operation": {
"pattern": ".log(",
"message": "Use log_operation() with success/error params, not .log()"
"message": "Use log_operation() with success/error params, not .log()",
},
"dict_none_no_check": {
"pattern": "Dict | None",
"message": "Dict | None return: Add None check before using (if result is None: return)"
}
"message": "Dict | None return: Add None check before using (if result is None: return)",
},
}
# JSON-specific patterns for emoji corruption
JSON_CORRUPTION_CHARS = ['\ufffd', '\x00']
JSON_CORRUPTION_CHARS = ["\ufffd", "\x00"]
def run_python_checks(file_path: str) -> list[str]:
@@ -75,10 +72,7 @@ def run_python_checks(file_path: str) -> list[str]:
# 1. Syntax check with py_compile
try:
result = subprocess.run(
[sys.executable, "-m", "py_compile", file_path],
capture_output=True,
text=True,
timeout=5
[sys.executable, "-m", "py_compile", file_path], capture_output=True, text=True, timeout=5
)
if result.returncode != 0:
errors.append(f"SYNTAX: {result.stderr.strip()}")
@@ -91,7 +85,7 @@ def run_python_checks(file_path: str) -> list[str]:
["ruff", "check", "--select=E,F,W", "--output-format=text", file_path],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
if result.stdout.strip():
for line in result.stdout.strip().split("\n")[:5]:
@@ -103,12 +97,7 @@ def run_python_checks(file_path: str) -> list[str]:
# 3. Ruff format check — detect format drift
try:
result = subprocess.run(
["ruff", "format", "--check", file_path],
capture_output=True,
text=True,
timeout=10
)
result = subprocess.run(["ruff", "format", "--check", file_path], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
errors.append(f"FORMAT: {Path(file_path).name} needs ruff format (run: ruff format {Path(file_path).name})")
except FileNotFoundError:
@@ -146,19 +135,20 @@ def run_python_checks(file_path: str) -> list[str]:
return errors
def run_ruff_lint_structured(file_path: str) -> list[dict]:
"""Run ruff check and return structured violations for the state file.
Returns list of {line, message} dicts — same format as pyright errors.
Only non-empty when ruff finds real violations (not format drift).
"""
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
result = subprocess.run(
["ruff", "check", "--select=E,F,W", "--output-format=json", file_path],
capture_output=True, text=True, timeout=10
capture_output=True,
text=True,
timeout=10,
)
if not result.stdout.strip():
return []
@@ -179,15 +169,12 @@ def run_ruff_lint_structured(file_path: str) -> list[dict]:
def run_pyright_check(file_path: str) -> list[dict]:
"""Run pyright on a single file. Returns list of error dicts."""
# Skip hook files - they don't follow project standards
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
result = subprocess.run(
[sys.executable, "-m", "pyright", "--outputjson", file_path],
capture_output=True,
text=True,
timeout=15
[sys.executable, "-m", "pyright", "--outputjson", file_path], capture_output=True, text=True, timeout=15
)
try:
@@ -201,10 +188,7 @@ def run_pyright_check(file_path: str) -> list[dict]:
if severity == "error":
line = diag.get("range", {}).get("start", {}).get("line", 0)
message = diag.get("message", "Unknown error")
errors.append({
"line": line,
"message": message[:100]
})
errors.append({"line": line, "message": message[:100]})
return errors[:10] # Max 10 errors
@@ -220,10 +204,7 @@ def save_diagnostics_state(file_path: str, errors: list[dict]):
"""Save type errors to state file for PreToolUse gate."""
try:
if errors:
state = {
"file": str(Path(file_path).resolve()),
"errors": errors
}
state = {"file": str(Path(file_path).resolve()), "errors": errors}
STATE_FILE.write_text(json.dumps(state), encoding="utf-8")
else:
# No errors — clear the state
@@ -249,11 +230,11 @@ def run_json_checks(file_path: str) -> list[str]:
data = json.loads(content)
if isinstance(data, dict):
for key in ['allowed_emojis', 'emojis', 'emoji_list']:
for key in ["allowed_emojis", "emojis", "emoji_list"]:
if key in data and isinstance(data[key], list):
for item in data[key]:
if isinstance(item, str) and len(item) == 1:
if ord(item) < 128 and item not in '\u2713\u2717':
if ord(item) < 128 and item not in "\u2713\u2717":
errors.append(f"EMOJI CORRUPTION: Suspicious char '{item}' in {key}")
break
@@ -268,7 +249,7 @@ def run_json_checks(file_path: str) -> list[str]:
def run_seedgo_checklist(file_path: str) -> list[str]:
"""Run seedgo standards checklist — returns violations only."""
if '/.claude/hooks/' in file_path:
if "/.claude/hooks/" in file_path:
return []
try:
@@ -277,7 +258,7 @@ def run_seedgo_checklist(file_path: str) -> list[str]:
capture_output=True,
text=True,
timeout=15,
cwd=str(Path.home() / "Projects" / "AIPass")
cwd=str(Path.home() / "Projects" / "AIPass"),
)
if result.returncode != 0:
@@ -369,17 +350,12 @@ def main():
Fix these errors in {Path(file_path).name} now. Do not skip or defer."""
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": context
},
"systemMessage": f"[AUTO-FIX] {len(errors)} error(s) — fix before continuing"
"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": context},
"systemMessage": f"[AUTO-FIX] {len(errors)} error(s) — fix before continuing",
}
print(json.dumps(output))
else:
output = {
"systemMessage": "[diagnostics] ok"
}
output = {"systemMessage": "[diagnostics] ok"}
print(json.dumps(output))
except Exception:
@@ -8,6 +8,7 @@ AI sees branch-specific context.
Version: 1.0.0
"""
from pathlib import Path
@@ -7,6 +7,7 @@ a notification if any exist.
Version: 1.0.0
"""
import json
from pathlib import Path
@@ -86,7 +87,9 @@ def main():
new_count = count_new_emails(branch_root)
if new_count > 0:
plural = "s" if new_count != 1 else ""
print(f"You have {new_count} new email{plural} - check with: drone @ai_mail inbox | then: drone @ai_mail view <id> | close with: drone @ai_mail close <id>")
print(
f"You have {new_count} new email{plural} - check with: drone @ai_mail inbox | then: drone @ai_mail view <id> | close with: drone @ai_mail close <id>"
)
if __name__ == "__main__":
@@ -7,6 +7,7 @@ Finds the branch root by walking up from CWD looking for apps/ or *.id.json.
Version: 1.0.0
"""
import json
from pathlib import Path