@@ -375,6 +375,200 @@ replace_date_placeholders "$SPEC_FILE"
375375CONTEXT_TEMPLATE=" $REPO_ROOT /.specify/templates/context-template.md"
376376CONTEXT_FILE=" $FEATURE_DIR /context.md"
377377
378+ # Function to discover team directives (Issue #47)
379+ discover_directives () {
380+ local feature_description=" $1 "
381+ local team_directives_path=" $2 "
382+
383+ if [[ ! -d " $team_directives_path " ]]; then
384+ cat << 'EOF '
385+ {
386+ "candidates": {
387+ "constitution": "",
388+ "personas": [],
389+ "rules": [],
390+ "skills": [],
391+ "examples": []
392+ },
393+ "search_metadata": {
394+ "keywords": [],
395+ "files_searched": 0,
396+ "files_with_matches": 0
397+ }
398+ }
399+ EOF
400+ return
401+ fi
402+
403+ local constitution=" "
404+ if [[ -f " $team_directives_path /constitutions/constitution.md" ]]; then
405+ constitution=" $team_directives_path /constitutions/constitution.md"
406+ elif [[ -f " $team_directives_path /constitution.md" ]]; then
407+ constitution=" $team_directives_path /constitution.md"
408+ fi
409+
410+ cat << EOF
411+ {
412+ "candidates": {
413+ "constitution": "${constitution} ",
414+ "personas": [],
415+ "rules": [],
416+ "skills": [],
417+ "examples": []
418+ },
419+ "search_metadata": {
420+ "keywords": [],
421+ "files_searched": 0,
422+ "files_with_matches": 0
423+ }
424+ }
425+ EOF
426+ }
427+
428+ # Function to discover skills (Issue #49)
429+ discover_skills () {
430+ local feature_description=" $1 "
431+ local team_directives_path=" $2 "
432+ local skills_cache_path=" $3 "
433+ local max_skills=" ${4:- 5} "
434+ local threshold=" ${5:- 0.7} "
435+
436+ mkdir -p " $skills_cache_path "
437+
438+ local cache_marker=" $skills_cache_path /.last_refresh"
439+ local current_timestamp=$( date +%s)
440+ local one_day=86400
441+
442+ local need_refresh=false
443+ if [[ -f " $cache_marker " ]]; then
444+ local last_refresh=$( cat " $cache_marker )
445+ local age=$(( current_timestamp - last_refresh))
446+ if [[ $age -gt $one_day ]]; then
447+ need_refresh=true
448+ fi
449+ else
450+ need_refresh=true
451+ fi
452+
453+ if $need_refresh && [[ -d " $team_directives_path /skills" ]]; then
454+ echo " [specify] Refreshing skills cache (daily refresh)..." >&2
455+ cp -r " $team_directives_path /skills/" * " $skills_cache_path /" 2>/dev/null || true
456+ echo " $current_timestamp " > " $cache_marker "
457+ fi
458+
459+ local required_skills=()
460+ local blocked_skills=()
461+ if [[ -f " $team_directives_path /.skills.json" ]]; then
462+ local required=$( jq -r ' .skills.required // {} | keys[]' " $team_directives_path /.skills.json" 2> /dev/null)
463+ [[ -n " $required " ]] && while read -r skill_id; do
464+ local skill_url=$( jq -r " .skills.required[\" $skill_id \" ].url // empty" " $team_directives_path /.skills.json" 2> /dev/null)
465+ if [[ -n " $skill_url " ]]; then
466+ local cache_dir=" $skills_cache_path /$( basename " $skill_id " ) "
467+ mkdir -p " $cache_dir "
468+ curl -s -o " $cache_dir /SKILL.md" " $skill_url " 2>/dev/null
469+ if [[ -f " $cache_dir /SKILL.md" && -s " $cache_dir /SKILL.md" ]]; then
470+ required_skills+=(" $skill_id " )
471+ fi
472+ elif [[ " $skill_id " == " local:" * ]]; then
473+ required_skills+=(" $skill_id " )
474+ elif [[ -d " $skills_cache_path /$skill_id " && -f " $skills_cache_path /$skill_id /SKILL.md" ]]; then
475+ required_skills+=(" $skill_id " )
476+ fi
477+ done <<< " $required "
478+
479+ local recommended=$( jq -r ' .skills.recommended // {} | keys[]' " $team_directives_path /.skills.json" 2> /dev/null)
480+ [[ -n " $recommended " ]] && while read -r skill_id; do
481+ local skill_url=$( jq -r " .skills.recommended[\" $skill_id \" ].url // empty" " $team_directives_path /.skills.json" 2> /dev/null)
482+ if [[ -n " $skill_url " ]]; then
483+ local cache_dir=" $skills_cache_path /$( basename " $skill_id " ) "
484+ mkdir -p " $cache_dir "
485+ curl -s -o " $cache_dir /SKILL.md" " $skill_url " 2>/dev/null
486+ if [[ -f " $cache_dir /SKILL.md" && -s " $cache_dir /SKILL.md" ]]; then
487+ required_skills+=(" $skill_id " )
488+ fi
489+ elif [[ " $skill_id " == " local:" * ]]; then
490+ required_skills+=(" $skill_id " )
491+ elif [[ -d " $skills_cache_path /$skill_id " && -f " $skills_cache_path /$skill_id /SKILL.md" ]]; then
492+ required_skills+=(" $skill_id " )
493+ fi
494+ done <<< " $recommended "
495+
496+ local blocked=$( jq -r ' .skills.blocked // [] | .[]' " $team_directives_path /.skills.json" 2> /dev/null)
497+ [[ -n " $blocked " ]] && while read -r blocked_id; do
498+ blocked_skills+=(" $blocked_id " )
499+ done <<< " $blocked "
500+ fi
501+
502+ local cached_skills=()
503+ if [[ -d " $skills_cache_path " ]]; then
504+ while IFS= read -r skill_dir; do
505+ [[ " $skill_dir " == " $skills_cache_path " ]] && continue
506+ [[ ! -d " $skill_dir " ]] && continue
507+ local skill_name=$( basename " $skill_dir " )
508+ if [[ " ${required_skills[*]} " =~ " $skill_name " ]]; then
509+ continue
510+ fi
511+ local skill_md=" $skill_dir /SKILL.md"
512+ if [[ ! -f " $skill_md " || ! -s " $skill_md " ]]; then
513+ continue
514+ fi
515+ cached_skills+=(" $skill_name " )
516+ done < <(find " $skills_cache_path " -maxdepth 1 -type d | grep -v " $skills_cache_path $" )
517+ fi
518+
519+ local candidate_list=()
520+ for skill_id in " ${required_skills[@]} " ; do
521+ local is_blocked=0
522+ for blocked_id in " ${blocked_skills[@]} " ; do
523+ [[ " $skill_id " == " $blocked_id " ]] && is_blocked=1 && break
524+ done
525+ [[ $is_blocked -eq 1 ]] && continue
526+
527+ local skill_path=" "
528+ local skill_name=" "
529+ if [[ " $skill_id " == " local:" * ]]; then
530+ skill_path=" $team_directives_path /${skill_id# local: } "
531+ skill_name=$( basename " $skill_path " )
532+ else
533+ skill_path=" $skills_cache_path /$skill_id "
534+ skill_name=$skill_id
535+ fi
536+
537+ [[ ! -f " $skill_path /SKILL.md" || ! -s " $skill_path /SKILL.md" ]] && continue
538+ candidate_list+=(" required:$skill_name " )
539+ done
540+
541+ for skill_name in " ${cached_skills[@]} " ; do
542+ local is_blocked=0
543+ for blocked_id in " ${blocked_skills[@]} " ; do
544+ [[ " $skill_name " == " $blocked_id " ]] && is_blocked=1 && break
545+ done
546+ [[ $is_blocked -eq 1 ]] && continue
547+ [[ " ${candidate_list[*]} " =~ " required:$skill_name " ]] && continue
548+ candidate_list+=(" $skill_name " )
549+ done
550+
551+ local candidates_json=" ["
552+ local first=1
553+ for skill_id in " ${candidate_list[@]: 0: $max_skills } " ; do
554+ [[ $first -eq 0 ]] && candidates_json+=" ,"
555+ local skill_name=" ${skill_id# required: } "
556+ local source=" ${skill_id%%:* } "
557+ local base_relevance=" 1.0"
558+ [[ " $source " != " required" ]] && base_relevance=" 0.65"
559+ candidates_json+=$( jq -n --arg id " $skill_id " --arg name " $skill_name " --arg source " $source " --argjson base_relevance " $base_relevance " ' {"id":$id,"name":$name,"source":$source,"base_relevance":$base_relevance}' )
560+ first=0
561+ done
562+ candidates_json+=" ]"
563+
564+ jq -n \
565+ --argjson candidates " $candidates_json " \
566+ '{
567+ " candidates" : $candidates ,
568+ " last_refresh" : " ' $(date -u +"%Y-%m-%dT%H:%M:%SZ")' "
569+ }'
570+ }
571+
378572# Function to populate context.md with defaults
379573populate_context_file() {
380574 local context_file=" $1 "
@@ -435,11 +629,37 @@ else
435629 touch " $CONTEXT_FILE "
436630fi
437631
632+ # Resolve team directives path
633+ TEAM_DIRECTIVES_DIR=" ${SPECIFY_TEAM_DIRECTIVES:- } "
634+ if [[ -z " $TEAM_DIRECTIVES_DIR " ]]; then
635+ TEAM_DIRECTIVES_DIR=" $REPO_ROOT /.specify/memory/team-ai-directives"
636+ fi
637+
638+ # Sync team-ai-directives if URL provided
639+ if [[ " $TEAM_DIRECTIVES_DIR " =~ ^https?:// ]]; then
640+ echo " [specify] Syncing team-ai-directives from $TEAM_DIRECTIVES_DIR ..." >&2
641+ TEMP_DIR=$( mktemp -d)
642+ if git clone --depth 1 " $TEAM_DIRECTIVES_DIR " " $TEMP_DIR /team-ai-directives" 2>/dev/null; then
643+ TARGET_DIR=" $REPO_ROOT /.specify/memory/team-ai-directives"
644+ rm -rf " $TARGET_DIR "
645+ mv " $TEMP_DIR /team-ai-directives" " $TARGET_DIR "
646+ TEAM_DIRECTIVES_DIR=" $TARGET_DIR "
647+ echo " [specify] Team-ai-directives synced successfully" >&2
648+ else
649+ echo " [specify] Warning: Failed to sync team-ai-directives" >&2
650+ fi
651+ rm -rf " $TEMP_DIR "
652+ fi
653+
654+ # Discover directives and skills
655+ DISCOVERED_DIRECTIVES=$( discover_directives " $FEATURE_DESCRIPTION " " $TEAM_DIRECTIVES_DIR " )
656+ DISCOVERED_SKILLS=$( discover_skills " $FEATURE_DESCRIPTION " " $TEAM_DIRECTIVES_DIR " " $REPO_ROOT /.specify/skills" )
657+
438658# Set the SPECIFY_FEATURE environment variable for the current session
439659export SPECIFY_FEATURE=" $BRANCH_NAME "
440660
441661if $JSON_MODE ; then
442- printf ' {"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' " $BRANCH_NAME " " $SPEC_FILE " " $FEATURE_NUM "
662+ printf '{" BRANCH_NAME" :" %s" ," SPEC_FILE" :" %s" ," FEATURE_NUM" :" %s" , " DISCOVERED_DIRECTIVES " :%s, " DISCOVERED_SKILLS " :%s }\n' " $BRANCH_NAME " " $SPEC_FILE " " $FEATURE_NUM " " $DISCOVERED_DIRECTIVES " " $DISCOVERED_SKILLS "
443663else
444664 echo " BRANCH_NAME: $BRANCH_NAME "
445665 echo " SPEC_FILE: $SPEC_FILE "
0 commit comments