Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/gitnexus/gitnexus-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Run from the project root. This parses all source files, builds the knowledge gr
| -------------- | ---------------------------------------------------------------- |
| `--force` | Force full re-index even if up to date |
| `--embeddings` | Enable embedding generation for semantic search (off by default) |
| `--drop-embeddings` | Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` preserves them. |

**When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook runs `analyze` automatically after `git commit` and `git merge`, preserving embeddings if previously generated.

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ jobs:
env:
# Commit-message hooks check `git log` HEAD — not meaningful in CI
SKIP: conventional-gitmoji,commitizen
- name: Check Ruff formatting
run: uv run ruff format --check --force-exclude .
- name: Check YAML formatting
run: >-
uv run yamllint -d "{extends: default, rules: {line-length: disable,
document-start: disable, truthy: disable, comments: disable}}"
.github/workflows

build:
runs-on: ubuntu-latest
Expand Down
90 changes: 17 additions & 73 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ Reference specific personas when requesting work:
- In `9agents/agent_interviewer`, empty `MANAGE_ALLOWED_IPS` still requires `MANAGE_AUTH_TOKEN` when that env is set; supervaizer’s admin IP middleware has no equivalent token fallback when the allowlist is empty.

<!-- gitnexus:start -->

# GitNexus — Code Intelligence

This project is indexed by GitNexus as **supervaizer** (2690 symbols, 8358 relationships, 231 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **supervaizer** (4837 symbols, 7278 relationships, 93 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

Expand All @@ -53,90 +52,35 @@ This project is indexed by GitNexus as **supervaizer** (2690 symbols, 8358 relat
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.

## When Debugging

1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3. `READ gitnexus://repo/supervaizer/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed

## When Refactoring

- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.

## Never Do

- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.

## Tools Quick Reference

| Tool | When to use | Command |
| ---------------- | ----------------------------- | ----------------------------------------------------------------------- |
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |

## Impact Risk Levels

| Depth | Meaning | Action |
| ----- | ------------------------------------- | --------------------- |
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
| d=3 | MAY NEED TESTING — transitive | Test if critical path |

## Resources

| Resource | Use for |
| -------------------------------------------- | ---------------------------------------- |
| `gitnexus://repo/supervaizer/context` | Codebase overview, check index freshness |
| `gitnexus://repo/supervaizer/clusters` | All functional areas |
| `gitnexus://repo/supervaizer/processes` | All execution flows |
| `gitnexus://repo/supervaizer/process/{name}` | Step-by-step execution trace |

## Self-Check Before Finishing

Before completing any code modification task, verify:

1. `gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3. `gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated

## Keeping the Index Fresh

After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:

```bash
npx gitnexus analyze
```

If the index previously included embeddings, preserve them by adding `--embeddings`:

```bash
npx gitnexus analyze --embeddings
```
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/supervaizer/context` | Codebase overview, check index freshness |
| `gitnexus://repo/supervaizer/clusters` | All functional areas |
| `gitnexus://repo/supervaizer/processes` | All execution flows |
| `gitnexus://repo/supervaizer/process/{name}` | Step-by-step execution trace |

To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
## Cross-Repo Groups

> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
This repository is listed under GitNexus **group(s): runwaize** (see `~/.gitnexus/groups/`). For cross-repo analysis, use MCP tools `impact`, `query`, and `context` with `repo` set to `@<groupName>` or `@<groupName>/<memberPath>` (paths match keys in that group’s `group.yaml`). Use `group_list` / `group_sync` for membership and sync. From the terminal: `npx gitnexus group list`, `npx gitnexus group sync <name>`, `npx gitnexus group impact <name> --target <symbol> --repo <group-path>`.

## CLI

| Task | Read this skill file |
| -------------------------------------------- | ----------------------------------------------------------- |
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |

<!-- gitnexus:end -->
2 changes: 1 addition & 1 deletion GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ just test-no-cov

### Development Conventions

* **Code Style:** The project uses `ruff` for linting and `black` for formatting. The configuration for these tools can be found in the `pyproject.toml` file.
* **Code Style:** The project uses `ruff` for linting and formatting. The configuration for these tools can be found in the `pyproject.toml` file.
* **Type Hinting:** The project uses type hinting extensively. The `mypy` tool is used for static type checking.
* **Commit Messages:** The project uses the `cz_gitmoji` convention for commit messages.
* **Pre-commit Hooks:** The project uses pre-commit hooks to enforce code style and run tests before committing code. The configuration for the pre-commit hooks can be found in the `.pre-commit-config.yaml` file.
Expand Down
21 changes: 21 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- **Agent method timeout metadata** — `AgentMethod` and the registration contract now expose `is_async` and `timeout` metadata for Studio. `timeout` defaults to 600 seconds and can be `null` for controller jobs that should run until Studio stops them manually.

### Changed

- **`pyproject.toml` (since v0.18.0)** — Runtime lower bounds: FastAPI, orjson, packaging, Pydantic, python-slugify, Rich, sse-starlette, TinyDB, Typer, Uvicorn. **`deploy` extra:** boto3 and Google Cloud libraries (Artifact Registry, Cloud Run, Secret Manager). **`dev` extra:** add `boto3`, `docker`, and `black` so `uv sync --extra dev` matches deploy/docker-heavy tests and Black in CI; add `yamllint`; bump mypy, pre-commit, pytest, respx, ruff. **`[tool.black]`:** explicit Black config (line length 88, Python 3.12–3.13 targets, excludes for caches/venv/dist).

### Tests

- `uv run pytest tests/test_agent.py tests/test_contracts.py`

`just test`

| Status | Count |
| ---------- | ----- |
| ✅ Passed | 570 |
| 🤔 Skipped | 0 |
| 🔴 Failed | 0 |
| ⏱️ in | 69s |

## [0.18.0] - 2026-05-04

## [0.17.3] - 2026-05-04
Expand Down
43 changes: 23 additions & 20 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ authors = [
]
dependencies = [
"art>=6.5",
"cryptography>=46.0.5",
"cryptography>=48.0.0",
"demjson3>=3.0.0",
"deprecated>=1.3.0",
"fastapi>=0.135.2",
"fastapi>=0.136.1",
"httpx>=0.28.1",
"jinja2>=3.1.6",
"loguru>=0.7.3",
"orjson>=3.11.7",
"packaging>=25.0",
"orjson>=3.11.9",
"packaging>=26.2.0",
"psutil>=7.2.2",
"pydantic>=2.12.0",
"python-slugify>=8.0.0",
"pydantic>=2.13.4",
"python-slugify>=8.0.4",
"pyyaml>=6.0.0",
"rich>=14.3.3",
"rich>=15.0.0",
"shortuuid>=1.0.0",
"sse-starlette>=3.3.3",
"tinydb>=4.8.0",
"typer>=0.24.1",
"uvicorn>=0.42.0",
"sse-starlette>=3.4.3",
"tinydb>=4.8.2",
"typer>=0.25.1",
"uvicorn>=0.46.0",
]
license = "MPL-2.0"
classifiers = [
Expand All @@ -50,24 +50,27 @@ supervaizer = "supervaizer.cli:app"

[project.optional-dependencies]
deploy = [
"boto3>=1.42.73",
"boto3>=1.43.6",
"docker>=7.0.0",
"google-cloud-artifact-registry>=1.20.0",
"google-cloud-run>=0.15.0",
"google-cloud-secret-manager>=2.26.0",
"google-cloud-artifact-registry>=1.21.0",
"google-cloud-run>=0.16.0",
"google-cloud-secret-manager>=2.28.0",
]
dev = [
"boto3>=1.42.73",
"docker>=7.0.0",
"hatch>=1.16.5",
"jsonschema>=4.26.0",
"mypy>=1.19.1",
"pre-commit>=4.5.1",
"mypy>=2.1.0",
"pre-commit>=4.6.0",
"pytest-asyncio>=1.3.0",
"pytest-cov>=7.1.0",
"pytest-mock>=3.15.1",
"pytest-sugar>=1.1.1",
"pytest>=8.5.0",
"respx>=0.22.0",
"ruff>=0.15.7",
"pytest>=9.0.3",
"respx>=0.23.1",
"ruff>=0.15.12",
"yamllint>=1.38.0",
"types-deprecated>=1.3.1",
"types-PyYAML>=6.0.0",
"types-python-slugify>=8.0.2.20240310",
Expand Down
39 changes: 28 additions & 11 deletions src/supervaizer/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ class AgentMethodAbstract(BaseModel):
is_async: bool = Field(
default=False, description="Whether the method is asynchronous"
)
timeout: int | None = Field(
default=600,
description=(
"Maximum automatic job duration in seconds. Use None for jobs that "
"must run until Studio stops them manually."
),
)

model_config = cast(
ConfigDict,
Expand Down Expand Up @@ -454,6 +461,8 @@ def registration_info(self) -> Dict[str, Any]:
"params": self.params,
"fields": self.fields_definitions,
"description": self.description,
"is_async": self.is_async,
"timeout": self.timeout,
"nodes": self.nodes.registration_info if self.nodes else None,
}

Expand Down Expand Up @@ -590,6 +599,10 @@ class AgentAbstract(SvBaseModel):
default=None, description="Editor (usually a company)"
)
version: str = Field(default="", description="Version string")
release_notes_url: str | None = Field(
default=None,
description="URL for release notes matching this agent version",
)
description: str = Field(
default="", description="Description of what the agent does"
)
Expand Down Expand Up @@ -658,6 +671,7 @@ def __init__(
maintainer: Optional[str] = None,
editor: Optional[str] = None,
version: str = "",
release_notes_url: str | None = None,
description: str = "",
tags: list[str] | None = None,
methods: AgentMethods | None = None,
Expand Down Expand Up @@ -687,6 +701,7 @@ def __init__(
maintainer (str, optional): Current maintainer
editor (str, optional): Current editor
version (str): Version string
release_notes_url (str, optional): URL for this version's release notes
description (str): Description of what the agent does
tags (list[str], optional): Tags for categorizing the agent
methods (AgentMethods): Methods supported by this agent
Expand All @@ -713,6 +728,7 @@ def __init__(
maintainer=maintainer,
editor=editor,
version=version,
release_notes_url=release_notes_url,
description=description,
tags=tags,
methods=methods,
Expand Down Expand Up @@ -759,6 +775,7 @@ def registration_info(self) -> Dict[str, Any]:
"maintainer": self.maintainer,
"editor": self.editor,
"version": self.version,
"release_notes_url": self.release_notes_url,
"description": self.description,
"api_path": self.path,
"slug": self.slug,
Expand Down Expand Up @@ -883,9 +900,15 @@ def job_start(
"""
if not self.methods:
raise ValueError("Agent methods not defined")
log.debug(
f"[Agent job_start] Run <{self.methods.job_start.method}> - Job <{job.id}>"
)

if method_name == "job_start":
action = self.methods.job_start
else:
if not self.methods.custom:
raise ValueError(f"Custom method {method_name} not found")
action = self.methods.custom[method_name]

log.debug(f"[Agent job_start] Run <{action.method}> - Job <{job.id}>")
# Mark job as in progress when execution starts
job.add_response(
JobResponse(
Expand All @@ -897,13 +920,6 @@ def job_start(
)

# Execute the method
if method_name == "job_start":
action = self.methods.job_start
else:
if not self.methods.custom:
raise ValueError(f"Custom method {method_name} not found")
action = self.methods.custom[method_name]

action_method = action.method
method_params = action.params or {}
params = (
Expand All @@ -916,7 +932,7 @@ def job_start(
f"[Agent job_start] action_method : {action_method} - params : {params}"
)
try:
if self.methods.job_start.is_async:
if action.is_async:
# TODO: Implement async job execution & test
raise NotImplementedError(
"[Agent job_start] Async job execution is not implemented"
Expand Down Expand Up @@ -1015,6 +1031,7 @@ class AgentResponse(BaseModel):
maintainer: Optional[str] = None
editor: Optional[str] = None
version: str
release_notes_url: Optional[str] = None
api_path: str
description: str
tags: Optional[list[str]] = None
Expand Down
3 changes: 3 additions & 0 deletions src/supervaizer/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ class AgentMethodContract(ContractModel):
params: dict[str, Any] | None = None
fields: list[AgentMethodFieldContract | dict[str, Any]] | None = None
description: str | None = None
is_async: bool = False
timeout: int | None = 600
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
nodes: dict[str, Any] | None = None


Expand Down Expand Up @@ -186,6 +188,7 @@ class AgentRegistrationContract(ContractModel):
slug: str
name: str
api_path: str
release_notes_url: str | None = None
methods: AgentMethodsContract | dict[str, Any] = Field(default_factory=dict)
parameters_setup: list[dict[str, Any]] = Field(default_factory=list)
data_resources: list[DataResourceContract | dict[str, Any]] = Field(
Expand Down
Loading
Loading