Skip to content

Commit 03e49a7

Browse files
committed
add support to local team-ai-directive
1 parent b90ed16 commit 03e49a7

File tree

5 files changed

+93
-11
lines changed

5 files changed

+93
-11
lines changed

scripts/bash/common.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#!/usr/bin/env bash
22
# Common functions and variables for all scripts
33

4+
# Shared constants
5+
TEAM_DIRECTIVES_DIRNAME="team-ai-directives"
6+
47
# Load gateway configuration and export helper environment variables
58
load_gateway_config() {
69
local repo_root="$1"
@@ -30,6 +33,30 @@ load_gateway_config() {
3033
fi
3134
}
3235

36+
load_team_directives_config() {
37+
local repo_root="$1"
38+
if [[ -n "${SPECIFY_TEAM_DIRECTIVES:-}" ]]; then
39+
return
40+
fi
41+
42+
local config_file="$repo_root/.specify/config/team_directives.path"
43+
if [[ -f "$config_file" ]]; then
44+
local path
45+
path=$(cat "$config_file" 2>/dev/null)
46+
if [[ -n "$path" && -d "$path" ]]; then
47+
export SPECIFY_TEAM_DIRECTIVES="$path"
48+
return
49+
else
50+
echo "[specify] Warning: team directives path '$path' from $config_file is unavailable." >&2
51+
fi
52+
fi
53+
54+
local default_dir="$repo_root/.specify/memory/$TEAM_DIRECTIVES_DIRNAME"
55+
if [[ -d "$default_dir" ]]; then
56+
export SPECIFY_TEAM_DIRECTIVES="$default_dir"
57+
fi
58+
}
59+
3360
# Get repository root, with fallback for non-git repositories
3461
get_repo_root() {
3562
if git rev-parse --show-toplevel >/dev/null 2>&1; then
@@ -115,6 +142,7 @@ get_feature_dir() { echo "$1/specs/$2"; }
115142
get_feature_paths() {
116143
local repo_root=$(get_repo_root)
117144
load_gateway_config "$repo_root"
145+
load_team_directives_config "$repo_root"
118146
local current_branch=$(get_current_branch)
119147
local has_git_repo="false"
120148

@@ -137,6 +165,7 @@ DATA_MODEL='$feature_dir/data-model.md'
137165
QUICKSTART='$feature_dir/quickstart.md'
138166
CONTEXT='$feature_dir/context.md'
139167
CONTRACTS_DIR='$feature_dir/contracts'
168+
TEAM_DIRECTIVES='${SPECIFY_TEAM_DIRECTIVES:-}'
140169
EOF
141170
}
142171

scripts/bash/create-new-feature.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,19 @@ else
6262
CONSTITUTION_FILE=""
6363
fi
6464

65-
TEAM_DIRECTIVES_DIR="$REPO_ROOT/.specify/memory/$TEAM_DIRECTIVES_DIRNAME"
65+
if [ -z "$SPECIFY_TEAM_DIRECTIVES" ]; then
66+
CONFIG_TEAM_FILE="$REPO_ROOT/.specify/config/team_directives.path"
67+
if [ -f "$CONFIG_TEAM_FILE" ]; then
68+
CONFIG_TEAM_PATH=$(cat "$CONFIG_TEAM_FILE")
69+
if [ -d "$CONFIG_TEAM_PATH" ]; then
70+
export SPECIFY_TEAM_DIRECTIVES="$CONFIG_TEAM_PATH"
71+
else
72+
>&2 echo "[specify] Warning: team directives path '$CONFIG_TEAM_PATH' from $CONFIG_TEAM_FILE is missing."
73+
fi
74+
fi
75+
fi
76+
77+
TEAM_DIRECTIVES_DIR="${SPECIFY_TEAM_DIRECTIVES:-$REPO_ROOT/.specify/memory/$TEAM_DIRECTIVES_DIRNAME}"
6678
if [ -d "$TEAM_DIRECTIVES_DIR" ]; then
6779
export SPECIFY_TEAM_DIRECTIVES="$TEAM_DIRECTIVES_DIR"
6880
else

scripts/bash/setup-plan.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ eval $(get_feature_paths)
3333
# Check if we're on a proper feature branch (only for git repos)
3434
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
3535

36+
# Resolve team directives path if provided
37+
if [[ -n "$TEAM_DIRECTIVES" && ! -d "$TEAM_DIRECTIVES" ]]; then
38+
echo "ERROR: TEAM_DIRECTIVES path $TEAM_DIRECTIVES is not accessible." >&2
39+
exit 1
40+
fi
41+
3642
# Ensure the feature directory exists
3743
mkdir -p "$FEATURE_DIR"
3844

@@ -71,7 +77,10 @@ else
7177
CONSTITUTION_FILE=""
7278
fi
7379

74-
TEAM_DIRECTIVES_DIR="${SPECIFY_TEAM_DIRECTIVES:-}"
80+
TEAM_DIRECTIVES_DIR="${TEAM_DIRECTIVES:-}"
81+
if [[ -z "$TEAM_DIRECTIVES_DIR" ]]; then
82+
TEAM_DIRECTIVES_DIR="${SPECIFY_TEAM_DIRECTIVES:-}"
83+
fi
7584
if [[ -z "$TEAM_DIRECTIVES_DIR" ]]; then
7685
TEAM_DIRECTIVES_DIR="$REPO_ROOT/.specify/memory/team-ai-directives"
7786
fi

src/specify_cli/__init__.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,21 @@ def _run_git_command(args: list[str], cwd: Path | None = None, *, env: dict[str,
399399
return subprocess.run(cmd, check=True, capture_output=True, text=True, env=env)
400400

401401

402-
def sync_team_ai_directives(repo_url: str, project_root: Path, *, skip_tls: bool = False) -> str:
403-
"""Clone or update the team-ai-directives repository under .specify/memory.
402+
def sync_team_ai_directives(repo_url: str, project_root: Path, *, skip_tls: bool = False) -> tuple[str, Path]:
403+
"""Clone or update the team-ai-directives repository.
404404
405-
Returns a short status string describing the action performed.
405+
If repo_url is a local path, return it directly and skip cloning.
406+
Returns a tuple of (status, path_to_use).
406407
"""
407408
repo_url = (repo_url or "").strip()
408409
if not repo_url:
409410
raise ValueError("Team AI directives repository URL cannot be empty")
410411

412+
# Detect local path
413+
potential_path = Path(repo_url).expanduser()
414+
if potential_path.exists() and potential_path.is_dir():
415+
return ("local", potential_path.resolve())
416+
411417
memory_root = project_root / ".specify" / "memory"
412418
memory_root.mkdir(parents=True, exist_ok=True)
413419
destination = memory_root / TEAM_DIRECTIVES_DIRNAME
@@ -432,14 +438,14 @@ def sync_team_ai_directives(repo_url: str, project_root: Path, *, skip_tls: bool
432438
_run_git_command(["remote", "set-url", "origin", repo_url], cwd=destination, env=git_env)
433439

434440
_run_git_command(["pull", "--ff-only"], cwd=destination, env=git_env)
435-
return "updated"
441+
return ("updated", destination)
436442

437443
if destination.exists() and not any(destination.iterdir()):
438444
shutil.rmtree(destination)
439445

440446
memory_root.mkdir(parents=True, exist_ok=True)
441447
_run_git_command(["clone", repo_url, str(destination)], env=git_env)
442-
return "cloned"
448+
return ("cloned", destination)
443449
except subprocess.CalledProcessError as exc:
444450
message = exc.stderr.strip() if exc.stderr else str(exc)
445451
raise RuntimeError(f"Git operation failed: {message}") from exc
@@ -1116,11 +1122,13 @@ def init(
11161122
suppress_warning=gateway_suppress_warning,
11171123
)
11181124

1125+
resolved_team_directives: Path | None = None
11191126
if team_ai_directives and team_ai_directives.strip():
11201127
tracker.start("directives", "syncing")
11211128
try:
1122-
directives_status = sync_team_ai_directives(team_ai_directives, project_path, skip_tls=skip_tls)
1123-
tracker.complete("directives", directives_status)
1129+
status, resolved_path = sync_team_ai_directives(team_ai_directives, project_path, skip_tls=skip_tls)
1130+
resolved_team_directives = resolved_path
1131+
tracker.complete("directives", status)
11241132
except Exception as e:
11251133
tracker.error("directives", str(e))
11261134
raise
@@ -1165,6 +1173,18 @@ def init(
11651173
# Final static tree (ensures finished state visible after Live context ends)
11661174
console.print(tracker.render())
11671175
console.print("\n[bold green]Project ready.[/bold green]")
1176+
1177+
# Persist team directives path if available
1178+
if resolved_team_directives is None:
1179+
default_dir = project_path / ".specify" / "memory" / TEAM_DIRECTIVES_DIRNAME
1180+
if default_dir.exists():
1181+
resolved_team_directives = default_dir
1182+
1183+
if resolved_team_directives is not None:
1184+
os.environ["SPECIFY_TEAM_DIRECTIVES"] = str(resolved_team_directives)
1185+
config_dir = project_path / ".specify" / "config"
1186+
config_dir.mkdir(parents=True, exist_ok=True)
1187+
(config_dir / "team_directives.path").write_text(str(resolved_team_directives))
11681188

11691189
# Agent folder security notice
11701190
agent_folder_map = {

tests/test_team_directives.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ def fake_run(cmd, check, capture_output, text, env=None):
1818

1919
monkeypatch.setattr(subprocess, "run", fake_run)
2020

21-
status = sync_team_ai_directives("https://example.com/repo.git", tmp_path, skip_tls=True)
21+
status, path = sync_team_ai_directives("https://example.com/repo.git", tmp_path, skip_tls=True)
2222

2323
assert status == "cloned"
24+
assert path == tmp_path / ".specify" / "memory" / TEAM_DIRECTIVES_DIRNAME
2425
memory_root = tmp_path / ".specify" / "memory"
2526
assert memory_root.exists()
2627
assert calls[0][0][:2] == ["git", "clone"]
@@ -42,9 +43,10 @@ def fake_run(cmd, check, capture_output, text, env=None):
4243

4344
monkeypatch.setattr(subprocess, "run", fake_run)
4445

45-
status = sync_team_ai_directives("https://example.com/repo.git", tmp_path)
46+
status, path = sync_team_ai_directives("https://example.com/repo.git", tmp_path)
4647

4748
assert status == "updated"
49+
assert path == destination
4850
assert any(item[3] == "pull" for item in commands if len(item) > 3)
4951
assert commands[0][:4] == ["git", "-C", str(destination), "rev-parse"]
5052

@@ -81,3 +83,13 @@ def fake_run(cmd, check, capture_output, text, env=None):
8183
sync_team_ai_directives("https://example.com/repo.git", tmp_path)
8284

8385
assert "fatal: error" in str(exc.value)
86+
87+
88+
def test_sync_returns_local_path_when_given_directory(tmp_path):
89+
local_repo = tmp_path / "team-ai-directives"
90+
local_repo.mkdir()
91+
92+
status, path = sync_team_ai_directives(str(local_repo), tmp_path)
93+
94+
assert status == "local"
95+
assert path == local_repo

0 commit comments

Comments
 (0)