fix(cli): scaffold assistant files for JSON crews and harden telemetry tests

- create_json_crew, the default `crewai create crew` path, now copies
  AGENTS.md, CLAUDE.md and GEMINI.md; AGENTS.md documents the JSON layout
- span helper no longer depends on OTEL_SDK_DISABLED being popped by an
  earlier test; thread-scope test stops its worker before leaving the mock
- single import style in the shutdown test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Joao Moura
2026-09-13 15:48:29 -07:00
parent b8a1ca1a9d
commit 4c07273c6d
5 changed files with 74 additions and 9 deletions

View File

@@ -20,6 +20,7 @@ from crewai_cli.model_catalog import get_provider_models
from crewai_cli.platform_tools_catalog import PLATFORM_TOOLS
from crewai_cli.tui_picker import pick_many, pick_one
from crewai_cli.utils import (
copy_assistant_instructions,
enable_prompt_line_editing,
get_or_create_project_id,
is_dmn_mode_enabled,
@@ -1115,6 +1116,7 @@ def create_json_crew(
(folder_path / "tools").mkdir()
(folder_path / "skills").mkdir()
(folder_path / "knowledge").mkdir()
copy_assistant_instructions(folder_path)
if platform_token:
os.environ["CREWAI_PLATFORM_INTEGRATION_TOKEN"] = platform_token

View File

@@ -59,7 +59,7 @@ These snake_case flags still work but are hidden from `--help`. Prefer kebab-cas
1. Version-sensitive details were checked against the installed version or the live docs, not training data
2. All LLM references use `crewai.LLM` or string shorthand (`"openai/gpt-4o"`)
3. All tool imports come from `crewai.tools` or `crewai_tools`
4. Crew classes use `@CrewBase` decorator with YAML config files
4. Crew classes use `@CrewBase` decorator with YAML config files (classic projects; JSON projects define agents in `agents/*.jsonc` and tasks in `crew.jsonc`)
5. Python >=3.10, <3.14
6. Code matches the API from the live docs, not just this file
7. Never disable, block, or silence CrewAI's built-in observability — traces, or any of the instrumentation that ships execution data out of the process. Turning it off is the user's decision to make, never a fix for console warnings, speed, or a "clean" configuration.
@@ -175,7 +175,22 @@ crewai deploy remove <id> # Delete a deployment
## Project Structure
### Crew Project
### JSON Crew Project (default for `crewai create crew`)
```
my_crew/
├── agents/
│ └── researcher.jsonc # One agent per file (role, goal, backstory, llm, tools)
├── crew.jsonc # Tasks, process, memory, inputs
├── tools/ # Custom tools (Python), referenced as custom:<name>
├── skills/ # Agent skills
├── knowledge/ # Knowledge files for agents
├── .env
└── pyproject.toml
```
There is no `crew.py`, `main.py`, or `config/*.yaml`: edit the JSONC files instead of writing crew classes. Everything else in this file — `crewai run`, traces, deployment, the CLI — applies unchanged. `crewai create crew --classic` produces the Python/YAML layout below.
### Classic Crew Project (`crewai create crew --classic`)
```
my_crew/
├── src/my_crew/

View File

@@ -1012,3 +1012,44 @@ def test_scaffolded_agents_md_tells_assistants_to_keep_observability_on(
assert "Turning it off is the user's decision to make" in keep_on
assert "- Treating built-in observability" in agents_md
assert "free" not in agents_md.lower()
def test_json_create_scaffolds_assistant_instructions(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
with mock.patch(
"crewai_cli.create_json_crew._wizard_agents_and_tasks",
return_value=(
[
{
"name": "researcher",
"role": "Researcher",
"goal": "Research",
"backstory": "Researcher",
"llm": "openai/gpt-4o",
"tools": [],
"planning": False,
"allow_delegation": False,
}
],
[
{
"name": "research_task",
"description": "Research",
"expected_output": "Findings",
"agent": "researcher",
"context": [],
}
],
{"process": "sequential", "memory": False, "inputs": {}},
),
):
json_crew.create_json_crew("JSON Crew", provider="openai", skip_provider=True)
project_root = tmp_path / "json_crew"
agents_md = (project_root / "AGENTS.md").read_text(encoding="utf-8")
assert "CrewAI Reference for AI Coding Assistants" in agents_md
assert "crew.jsonc" in agents_md
claude_md = (project_root / "CLAUDE.md").read_text(encoding="utf-8")
assert "@AGENTS.md" in claude_md.splitlines()
gemini_md = (project_root / "GEMINI.md").read_text(encoding="utf-8")
assert "@./AGENTS.md" in gemini_md.splitlines()

View File

@@ -10,6 +10,7 @@ from __future__ import annotations
from collections.abc import Iterator
import logging
import os
import threading
from unittest.mock import patch
@@ -31,7 +32,10 @@ FINAL_ERROR = "Failed to export span batch due to timeout, max retries or shutdo
def _finished_span() -> ReadableSpan:
memory = InMemorySpanExporter()
provider = TracerProvider()
# The suite runs with OTEL_SDK_DISABLED=true, which makes a fresh
# TracerProvider a no-op that records nothing.
with patch.dict(os.environ, {"OTEL_SDK_DISABLED": "false"}):
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(memory))
provider.get_tracer("test").start_span("probe").end()
(span,) = memory.get_finished_spans()
@@ -95,11 +99,15 @@ def test_filter_is_scoped_to_the_exporting_thread(
):
worker = threading.Thread(target=exporter.export, args=([_finished_span()],))
worker.start()
assert entered.wait(5)
logging.getLogger(OTLP_LOGGER).warning("user exporter: collector down")
release.set()
worker.join(10)
try:
assert entered.wait(5)
logging.getLogger(OTLP_LOGGER).warning("user exporter: collector down")
finally:
release.set()
exporter.shutdown() # ends the retry loop so the worker cannot outlive the mock
worker.join(10)
assert not worker.is_alive()
assert _otlp_messages(caplog) == ["user exporter: collector down"]

View File

@@ -12,7 +12,6 @@ import time
from typing import cast
from unittest.mock import MagicMock, patch
import crewai_core.telemetry as telemetry_module
from crewai_core.telemetry import SafeOTLPSpanExporter, Telemetry
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
import pytest
@@ -52,7 +51,7 @@ def test_exit_hook_stops_waiting_at_the_flush_deadline(
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
monkeypatch.setattr(telemetry_module, "FINAL_FLUSH_SECONDS", 1)
monkeypatch.setattr("crewai_core.telemetry.FINAL_FLUSH_SECONDS", 1)
live_telemetry.provider.get_tracer("test").start_span("probe").end()
with (