diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ed2f6b5bc..c926827ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to the Specify CLI and templates are documented here. +# [0.12.15+adlc10] - 2026-07-19 + +### Added + +- **Neutral team-ai-directives scaffolding**: Added + `specify team-directives init [PATH] [--name NAME]` to create a consumable + team knowledge base without cloning Tikal-specific directives. The command + generates neutral manifest, constitution, CDR, skill registry, MCP example, + agent navigation, and tracked empty context directories. It does not create + team policy, initialize Git, or attach the knowledge base to a project. +- **Safe scaffold writes**: New destinations and existing empty directories are + supported. Files, symlinks, and non-empty directories are rejected, and + partial filesystem writes are rolled back. +- **Capability reporting**: `specify version --features` now advertises + `team_directives_init` for automation and agent feature detection. + # [0.12.15+adlc9] - 2026-07-17 ### Fixed diff --git a/FORK.md b/FORK.md index caeb3513b8..f858596050 100644 --- a/FORK.md +++ b/FORK.md @@ -72,6 +72,7 @@ When a fork release changes only bundled extension behavior, keep the CLI versio | Version | Date | Base Upstream | Changes | |---------|------|---------------|---------| +| 0.12.15+adlc10 | 2026-07-19 | 0.12.15 (`ad601e5d`) | Added `specify team-directives init [PATH] [--name NAME]` for creating a neutral, consumable team-ai-directives knowledge base without cloning Tikal defaults. The scaffold includes manifest/version metadata, an explicitly empty constitution and CDR index, compatible empty skill registries, an MCP example, AGENTS.md navigation, and tracked context/skill directories. It accepts absent or empty destinations, rejects occupied or symlink targets, rolls back partial writes, does not initialize Git, and advertises the capability through `specify version --features`. | | 0.12.15+adlc9 | 2026-07-17 | 0.12.15 (`ad601e5d`) | Bug fixes in team-boot and team-discover model-invocation commands: (1) team-boot Step 2 used glob to find constitution — `{TEAM_AI_DIRECTIVES}` was treated as a search target, not a resolved value; added explicit definition after Step 1 and direct-read instruction in Step 2. (2) team-discover Step 2 had no plain-message fallback — expected `{REPO_ROOT}/specs/${SPECIFY_FEATURE}/context.md` which doesn't exist when invoked as a skill from team-boot; added fallback to extract feature context from user's message. (3) team-discover mode detection missing skill-invocation case — all 4 rules depended on `$ARGUMENTS`/env vars/hook context; added rule 5: skill invocation defaults to no-write mode with inline output. (4) team-discover Step 4 didn't surface external skills — `.skills.json` `external` map entries never matched against feature context; expanded matching to cover both `default` and `external` lists with category-based matching. (5) team-discover Step 1 `{TEAM_AI_DIRECTIVES}` undefined — same bare-variable pattern as team-boot; added clarifying note. team-ai-directives extension 4.3.2 → 4.3.3. | | 0.12.15+adlc8 | 2026-07-17 | 0.12.15 (`ad601e5d`) | Canonical runtime event unification + 4 new hook-capable integrations. Extensions now use unified canonical event names (`PreToolUse`, `PostToolUse`, `Stop`, `SessionStart`, `SessionEnd`, `UserPromptSubmit`) in `runtime_hooks:`; each adapter translates to agent-native names via `CANONICAL_TO_NATIVE` mapping. New adapters: `QwenHookAdapter` (`.qwen/settings.json`), `GeminiHookAdapter` (`.gemini/settings.json`, `BeforeTool`/`AfterTool`), `DevinHookAdapter` (`.devin/hooks.v1.json`), `TabnineHookAdapter` (`.tabnine/agent/settings.json`). `validate_runtime_hooks()` rejects unknown event names. Adapters warn+skip unsupported events. `JSONHookAdapter` refactored with shared `_build_nested_fragment()` + `bridge_path_prefix`. Hook callout added to `TomlIntegration.setup()` + `YamlIntegration.setup()`. `--hooks` option added to gemini/qwen/devin/tabnine. 56 tests (22 new). | | 0.12.15+adlc7 | 2026-07-16 | 0.12.15 (`ad601e5d`) | Bug fix: team-* model-invocation commands (`team.boot`, `team.discover`, `team.skills`) silently no-op on dotfile paths. Step 1 "Locate Knowledge Base" used ambiguous "Read `.specify/init-options.json`" instruction — agents interpreted as "search first" and used glob/find tools, which silently skip dotfile-prefixed path segments (`.specify/`). team-boot's Step 1 exit handler fired, skipping constitution load and team-discover invocation entirely, leaving the full team-ai-directives knowledge base invisible for the session. Replaced with explicit agent-agnostic instruction: "Read the file directly. Do NOT use glob, find, or any file-search tool." Adds walk-up fallback via successive direct reads (`../.specify/`, `../../.specify/`) for CWD resilience. team-ai-directives extension 4.3.1 → 4.3.2. | diff --git a/QUICKSTART.md b/QUICKSTART.md index ca9084db52..1ac0eb4b88 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -63,6 +63,19 @@ Our workflow supports multiple AI agents. Choose your preferred tool: ## 2. Project Initialization +### Create Team AI Directives + +If your team does not already have a directives repository, create a neutral +one before initializing the project: + +```bash +specify team-directives init ../team-ai-directives --name my-team +``` + +This creates the required knowledge-base structure without defining team +policies or initializing Git. Add and review your team's principles and context, +then place the repository under version control when ready. + ### For New Projects Initialize a new project with team AI directives: @@ -72,8 +85,8 @@ Initialize a new project with team AI directives: mkdir my-project cd my-project -# Initialize with team-ai-directives -specify init . --team-ai-directives +# Initialize with a local team-ai-directives knowledge base +specify init . --team-ai-directives ../team-ai-directives ``` ### For Existing Projects diff --git a/README.md b/README.md index d1fd18c7f6..80a88163b0 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,23 @@ This fork includes pre-installed presets (auto-installed during `specify init`): ## 🔧 Team AI Directives Integration -This fork supports team-ai-directives — a foundation for version-controlled AI agent behavior. Install during project initialization: +This fork supports team-ai-directives — a foundation for version-controlled AI agent behavior. + +Create a neutral knowledge base for a new team: + +```bash +# Creates ./team-ai-directives by default +specify team-directives init + +# Or choose a path and manifest name +specify team-directives init ../platform-directives --name platform-team +``` + +The scaffold contains the required repository structure but no team policies, +personas, examples, skills, or MCP servers. It does not initialize Git. Review +and version the generated files according to your team's repository policy. + +Install a knowledge base during project initialization: ```bash # Install from GitHub archive (ZIP download) diff --git a/docs/reference/core.md b/docs/reference/core.md index ea3c4794a8..3993fc3e96 100644 --- a/docs/reference/core.md +++ b/docs/reference/core.md @@ -58,6 +58,35 @@ specify init my-project --integration copilot --preset compliance > **Symlinked project roots.** `SPECIFY_INIT_DIR` relocates *where* the project is, not *how* a command treats symlinks: each command keeps its existing cwd-path stance. Commands that traverse and write project files through broad input paths (`bundle`, `workflow run `) refuse a symlinked `.specify/` to preserve write confinement. Other project-scoped commands keep their existing behavior when `SPECIFY_INIT_DIR` points at a project root, which may include following a symlinked `.specify/`. +## Initialize Team AI Directives + +```bash +specify team-directives init [] [--name ] +``` + +Creates a neutral team-ai-directives knowledge base at ``, which defaults +to `./team-ai-directives`. When `--name` is omitted, the destination directory +name is stored in `manifest.yml`. + +The generated repository includes an explicitly empty constitution and CDR +index, empty context and skill registries, an MCP configuration example, and +navigation for agents. It contains no team policy and does not initialize Git +or modify a Spec Kit project. + +The destination may be absent or an existing empty directory. The command +rejects files, symlinks, and non-empty directories instead of overwriting them. + +```bash +# Use the default path +specify team-directives init + +# Choose a location and team name +specify team-directives init ../platform-directives --name platform-team + +# Consume the local knowledge base during project initialization +specify init my-project --team-ai-directives ../platform-directives +``` + ## Check Installed Tools ```bash diff --git a/pyproject.toml b/pyproject.toml index 118323c1d9..245dbd0d64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agentic-sdlc-specify-cli" -version = "0.12.15+adlc9" +version = "0.12.15+adlc10" description = "Specify CLI (tikalk fork). Agentic SDLC toolkit for Spec-Driven Development with pre-installed extensions and AI integrations." readme = "README.md" requires-python = ">=3.11" diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index f38a7d08c5..4abcfb7af1 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -565,6 +565,7 @@ def _feature_capabilities() -> dict[str, bool]: "multi_install_safe_registry_metadata": True, "integration_upgrade_command": True, "self_check_command": True, + "team_directives_init": True, "workflow_catalog": True, "bundled_templates": True, } @@ -692,6 +693,12 @@ def _require_specify_project() -> Path: _register_bundle_cmds(app) +# ===== Team Directives Commands ===== + +from .commands.team_directives import register as _register_team_directives_cmds # noqa: E402 +_register_team_directives_cmds(app) + + # ===== Workflow Commands ===== # Moved to workflows/_commands.py — registered here to preserve CLI surface. diff --git a/src/specify_cli/commands/team_directives/__init__.py b/src/specify_cli/commands/team_directives/__init__.py new file mode 100644 index 0000000000..5970e94bbe --- /dev/null +++ b/src/specify_cli/commands/team_directives/__init__.py @@ -0,0 +1,63 @@ +"""``specify team-directives`` command group.""" + +from __future__ import annotations + +from pathlib import Path + +import typer + +from ..._console import console, err_console + +try: + from ..._init_fork import accent, make_typer +except ImportError: + + def accent(text: str, **_kwargs) -> str: + return text + + def make_typer(*, name=None, help=None, **kwargs): + kwargs.setdefault("add_completion", False) + return typer.Typer(name=name, help=help, **kwargs) + + +team_directives_app = make_typer( + name="team-directives", + help="Create and manage team-ai-directives knowledge bases", +) + + +@team_directives_app.command("init") +def team_directives_init( + path: Path = typer.Argument( + Path("team-ai-directives"), + help="Destination directory", + ), + name: str | None = typer.Option( + None, + "--name", + help="Team name stored in manifest.yml (defaults to the directory name)", + ), +): + """Create a neutral team-ai-directives knowledge base.""" + from ...team_directives_scaffold import scaffold_team_directives + + try: + result = scaffold_team_directives(path, name) + except (OSError, ValueError) as exc: + err_console.print(f"[red]Error:[/red] {exc}") + raise typer.Exit(1) + + console.print( + f"{accent('Created team-ai-directives scaffold:', bold=True)} {result.root}" + ) + console.print( + f"[dim]{len(result.files)} files created; no Git repository initialized.[/dim]" + ) + console.print() + console.print("[bold]Next step:[/bold]") + console.print(f'specify init --team-ai-directives "{result.root}"') + + +def register(app: typer.Typer) -> None: + """Register the team-directives command group on the root app.""" + app.add_typer(team_directives_app, name="team-directives") diff --git a/src/specify_cli/team_directives_scaffold.py b/src/specify_cli/team_directives_scaffold.py new file mode 100644 index 0000000000..bd50e77f9d --- /dev/null +++ b/src/specify_cli/team_directives_scaffold.py @@ -0,0 +1,207 @@ +"""Create neutral team-ai-directives knowledge-base scaffolds.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from datetime import date +from pathlib import Path + + +@dataclass(frozen=True) +class TeamDirectivesScaffoldResult: + """Files produced by a team-ai-directives scaffold run.""" + + name: str + root: Path + files: tuple[Path, ...] + + +def _manifest_content(name: str, created_at: str) -> str: + return f"""schema_version: \"1.0\" +team_ai_directives: + name: {json.dumps(name)} + version: \"0.1.0\" + owner: \"\" + compatibility: + speckit: \">=0.12.0\" + created_at: \"{created_at}\" +""" + + +def _readme_content(name: str, directory_name: str) -> str: + path_example = json.dumps(f"/path/to/{directory_name}") + return f"""# {name} + +This is a neutral team-ai-directives knowledge base. It contains no team +policies, personas, examples, or skills until your team adds them. + +## Getting Started + +1. Define team-wide principles in `context_modules/constitution.md`. +2. Add focused rules, personas, and examples under `context_modules/`. +3. Add reusable skills under `skills/` and register them in `.skills.json`. +4. Record accepted context modules in `CDR.md`. + +Use this knowledge base when initializing a Spec Kit project: + +```bash +specify init --team-ai-directives {path_example} +``` + +`.mcp.json.example` is intentionally empty. Copy it to `.mcp.json` only when +the team needs shared MCP configuration, and never commit credentials. +""" + + +def _agents_content() -> str: + return """# Agent Instructions + +This repository stores team-owned context for AI agents. The scaffold itself +defines no behavioral policy. + +## Structure + +- `context_modules/constitution.md` contains team-wide principles. +- `context_modules/rules/`, `personas/`, and `examples/` contain focused context. +- `skills/` contains reusable skills registered in `.skills.json`. +- `CDR.md` indexes accepted context modules. + +## Loading Order + +Read the constitution first, then load only context relevant to the current +task. + +## Functional Categories (Rules) + +Organize rules by the technologies or concerns they govern. + +## Using Skills + +Treat `.skills.json` as the skill registry. The initial registry is empty. + +## CDR.md + +Add a Context Directive Record when the team accepts a context module. +""" + + +def _constitution_content(created_at: str) -> str: + return f"""--- +type: Constitution +title: Team Constitution +description: Team-wide principles governing AI agent behavior +tags: [governance, constitution] +timestamp: {created_at}T00:00:00Z +--- + +# Team Constitution + +No team-wide principles have been defined yet. +""" + + +def _cdr_content(created_at: str) -> str: + return f"""# Context Directive Records + +Context Directive Records (CDRs) track decisions about contributing context +modules to team-ai-directives. + +## CDR Index + +| ID | Target Module | Type | Status | Created | Verified | Age | Descriptor | +|----|---------------|------|--------|---------|----------|-----|------------| + +**Stats**: 0 entries | Last Updated: {created_at} +""" + + +def _scaffold_files(name: str, directory_name: str, created_at: str) -> dict[Path, str]: + skills_manifest = { + "version": "2.0.0", + "default": [], + "skills": {}, + "external": {}, + } + mcp_example = {"mcpServers": {}} + + return { + Path("manifest.yml"): _manifest_content(name, created_at), + Path("README.md"): _readme_content(name, directory_name), + Path("AGENTS.md"): _agents_content(), + Path("CDR.md"): _cdr_content(created_at), + Path(".skills.json"): json.dumps(skills_manifest, indent=2) + "\n", + Path(".mcp.json.example"): json.dumps(mcp_example, indent=2) + "\n", + Path("context_modules/constitution.md"): _constitution_content(created_at), + Path("context_modules/rules/.gitkeep"): "", + Path("context_modules/personas/.gitkeep"): "", + Path("context_modules/examples/.gitkeep"): "", + Path("skills/.gitkeep"): "", + } + + +def scaffold_team_directives( + target: Path, + name: str | None = None, + *, + created_at: date | None = None, +) -> TeamDirectivesScaffoldResult: + """Create a neutral, consumable team-ai-directives knowledge base.""" + root = target.expanduser().absolute() + if root.is_symlink(): + raise ValueError(f"Refusing to scaffold into a symlink: {root}") + if root.exists() and not root.is_dir(): + raise FileExistsError(f"Destination exists and is not a directory: {root}") + if root.exists() and any(root.iterdir()): + raise FileExistsError(f"Destination directory is not empty: {root}") + + scaffold_name = (name if name is not None else root.name).strip() + if not scaffold_name: + raise ValueError("Team directives name cannot be empty") + + created_on = (created_at or date.today()).isoformat() + content_by_path = _scaffold_files(scaffold_name, root.name, created_on) + created_files: list[Path] = [] + created_dirs: list[Path] = [] + + try: + if not root.exists(): + missing_roots: list[Path] = [] + parent = root + while not parent.exists(): + missing_roots.append(parent) + parent = parent.parent + root.mkdir(parents=True) + created_dirs.extend(reversed(missing_roots)) + + for relative_path, content in content_by_path.items(): + destination = root / relative_path + missing_parents: list[Path] = [] + parent = destination.parent + while parent != root and not parent.exists(): + missing_parents.append(parent) + parent = parent.parent + destination.parent.mkdir(parents=True, exist_ok=True) + created_dirs.extend(reversed(missing_parents)) + created_files.append(destination) + destination.write_text(content, encoding="utf-8") + except BaseException: + for path in reversed(created_files): + try: + path.unlink() + except OSError: + pass + for path in sorted( + set(created_dirs), key=lambda item: len(item.parts), reverse=True + ): + try: + path.rmdir() + except OSError: + pass + raise + + return TeamDirectivesScaffoldResult( + name=scaffold_name, + root=root, + files=tuple(root / path for path in content_by_path), + ) diff --git a/tests/test_cli_version.py b/tests/test_cli_version.py index 041ff62e55..63b589764a 100644 --- a/tests/test_cli_version.py +++ b/tests/test_cli_version.py @@ -66,6 +66,7 @@ def test_version_features_json(self): "multi_install_safe_registry_metadata": True, "integration_upgrade_command": True, "self_check_command": True, + "team_directives_init": True, "workflow_catalog": True, "bundled_templates": True, }, diff --git a/tests/test_commands_package.py b/tests/test_commands_package.py index b8cd262e89..88c626b636 100644 --- a/tests/test_commands_package.py +++ b/tests/test_commands_package.py @@ -13,6 +13,12 @@ def test_commands_init_importable(): assert callable(mod.register) +def test_commands_team_directives_importable(): + mod = importlib.import_module("specify_cli.commands.team_directives") + assert hasattr(mod, "register") + assert callable(mod.register) + + def test_agent_config_importable(): from specify_cli._agent_config import ( AGENT_CONFIG, diff --git a/tests/test_team_directives_scaffold.py b/tests/test_team_directives_scaffold.py new file mode 100644 index 0000000000..b6d880efe4 --- /dev/null +++ b/tests/test_team_directives_scaffold.py @@ -0,0 +1,214 @@ +"""Tests for neutral team-ai-directives scaffolding.""" + +from __future__ import annotations + +import json +from datetime import date +from pathlib import Path + +import pytest +import yaml +from typer.testing import CliRunner + +from specify_cli import _feature_capabilities, app +from specify_cli.team_directives_scaffold import scaffold_team_directives + + +runner = CliRunner() +EXPECTED_FILES = { + ".mcp.json.example", + ".skills.json", + "AGENTS.md", + "CDR.md", + "README.md", + "context_modules/constitution.md", + "context_modules/examples/.gitkeep", + "context_modules/personas/.gitkeep", + "context_modules/rules/.gitkeep", + "manifest.yml", + "skills/.gitkeep", +} + + +def _generated_files(root: Path) -> set[str]: + return { + path.relative_to(root).as_posix() for path in root.rglob("*") if path.is_file() + } + + +def test_scaffold_creates_neutral_valid_knowledge_base(tmp_path: Path): + root = tmp_path / "platform-team" + + result = scaffold_team_directives( + root, + created_at=date(2026, 7, 19), + ) + + assert result.name == "platform-team" + assert result.root == root + assert _generated_files(root) == EXPECTED_FILES + assert len(result.files) == len(EXPECTED_FILES) + assert not (root / ".git").exists() + + manifest = yaml.safe_load((root / "manifest.yml").read_text(encoding="utf-8")) + assert manifest == { + "schema_version": "1.0", + "team_ai_directives": { + "name": "platform-team", + "version": "0.1.0", + "owner": "", + "compatibility": {"speckit": ">=0.12.0"}, + "created_at": "2026-07-19", + }, + } + + skills = json.loads((root / ".skills.json").read_text(encoding="utf-8")) + assert skills["default"] == [] + assert skills["external"] == {} + assert skills["skills"] == {} + assert json.loads((root / ".mcp.json.example").read_text(encoding="utf-8")) == { + "mcpServers": {} + } + + constitution = (root / "context_modules/constitution.md").read_text( + encoding="utf-8" + ) + assert "type: Constitution" in constitution + assert "No team-wide principles have been defined yet." in constitution + + +def test_scaffold_accepts_existing_empty_directory_and_name_override(tmp_path: Path): + root = tmp_path / "directives" + root.mkdir() + + result = scaffold_team_directives(root, "Platform Team") + + assert result.name == "Platform Team" + manifest = yaml.safe_load((root / "manifest.yml").read_text(encoding="utf-8")) + assert manifest["team_ai_directives"]["name"] == "Platform Team" + readme = (root / "README.md").read_text(encoding="utf-8") + assert '"/path/to/directives"' in readme + assert "/path/to/Platform Team" not in readme + + +@pytest.mark.parametrize("target_kind", ["file", "non-empty-directory"]) +def test_scaffold_rejects_occupied_destination(tmp_path: Path, target_kind: str): + root = tmp_path / "team-ai-directives" + if target_kind == "file": + root.write_text("occupied", encoding="utf-8") + else: + root.mkdir() + (root / "existing.md").write_text("occupied", encoding="utf-8") + + with pytest.raises(FileExistsError): + scaffold_team_directives(root) + + +def test_scaffold_rejects_symlink_destination(tmp_path: Path): + actual = tmp_path / "actual" + actual.mkdir() + link = tmp_path / "team-ai-directives" + link.symlink_to(actual, target_is_directory=True) + + with pytest.raises(ValueError, match="symlink"): + scaffold_team_directives(link) + + assert list(actual.iterdir()) == [] + + +def test_scaffold_rolls_back_partial_write(tmp_path: Path, monkeypatch): + root = tmp_path / "generated" / "nested" / "team-ai-directives" + original_write_text = Path.write_text + writes = 0 + + def fail_second_write(path: Path, *args, **kwargs): + nonlocal writes + writes += 1 + if writes == 2: + raise RuntimeError("simulated write failure") + return original_write_text(path, *args, **kwargs) + + monkeypatch.setattr(Path, "write_text", fail_second_write) + + with pytest.raises(RuntimeError, match="simulated write failure"): + scaffold_team_directives(root) + + assert not root.exists() + assert not (tmp_path / "generated").exists() + + +def test_cli_uses_default_path_and_reports_next_step(tmp_path: Path, monkeypatch): + monkeypatch.chdir(tmp_path) + + result = runner.invoke(app, ["team-directives", "init"]) + + assert result.exit_code == 0, result.output + root = tmp_path / "team-ai-directives" + assert _generated_files(root) == EXPECTED_FILES + assert "Created team-ai-directives scaffold" in result.output + assert "no Git repository initialized" in result.output + assert "specify init --team-ai-directives" in result.output + + +def test_cli_help_and_name_option(tmp_path: Path): + help_result = runner.invoke(app, ["team-directives", "init", "--help"]) + assert help_result.exit_code == 0 + assert "--name" in help_result.output + + root = tmp_path / "custom" + result = runner.invoke( + app, + ["team-directives", "init", str(root), "--name", "Platform Team"], + ) + assert result.exit_code == 0, result.output + assert 'name: "Platform Team"' in (root / "manifest.yml").read_text( + encoding="utf-8" + ) + + +def test_cli_rejects_non_empty_destination(tmp_path: Path): + root = tmp_path / "team-ai-directives" + root.mkdir() + (root / "existing.md").write_text("keep", encoding="utf-8") + + result = runner.invoke(app, ["team-directives", "init", str(root)]) + + assert result.exit_code == 1 + assert "Destination directory is not empty" in result.output + assert (root / "existing.md").read_text(encoding="utf-8") == "keep" + + +def test_generated_scaffold_is_accepted_by_existing_sync_flow( + tmp_path: Path, + monkeypatch, +): + from specify_cli import _init_fork + + root = tmp_path / "team-ai-directives" + project = tmp_path / "project" + project.mkdir() + scaffold_team_directives(root) + + class FakeRegistry: + @staticmethod + def is_installed(_name: str) -> bool: + return True + + class FakeExtensionManager: + registry = FakeRegistry() + + monkeypatch.setattr( + _init_fork, + "ExtensionManager", + lambda _project_root: FakeExtensionManager(), + ) + monkeypatch.setattr(_init_fork, "_update_agent_context", lambda _path: None) + + status, resolved = _init_fork.sync_team_ai_directives(str(root), project) + + assert status == "local" + assert resolved == root + + +def test_feature_capabilities_advertise_team_directives_init(): + assert _feature_capabilities()["team_directives_init"] is True