diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c0b20af2c..96cfc60259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to the Specify CLI and templates are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to to [Semantic Versioning](https://semver.org/spec/v2.0.0/). +## [0.0.116] - 2026-03-08 + +### Fixed + +- **create-new-feature.sh Syntax Error**: Fixed unclosed quote in `discover_skills()` function at line 444 that caused bash syntax errors +- **git fetch Output Suppression**: Fixed `git fetch --all --prune` output being captured into `BRANCH_NUMBER` variable causing "10#Fetching" errors + ## [0.0.115] - 2026-03-08 ### Added diff --git a/docs/discovery.md b/docs/discovery.md new file mode 100644 index 0000000000..f6eef78c8c --- /dev/null +++ b/docs/discovery.md @@ -0,0 +1,440 @@ +# Context Auto-Discovery & Skills Discovery + +This document describes the automatic discovery system for team directives and skills in the Agentic SDLC Spec Kit. + +## Overview + +The discovery system provides a **two-tier architecture**: + +1. **Layer 1 (Scripts)**: Fast, deterministic baseline discovery using grep search and manifest-based lookup +2. **Layer 2 (Templates)**: AI-powered semantic enhancement in command templates + +### Benefits + +- **Fast Baseline**: Scripts provide quick candidate discovery without AI latency +- **Semantic Enhancement**: AI agents refine selections based on context and relevance +- **Team-Centric**: Uses team-ai-directives as primary knowledge source +- **Local-First**: Works offline with cached repository clones + +## Team AI Directives Integration + +### Structure + +``` +team-ai-directives/ +├── constitutions/ # Team principles and constraints +│ └── constitution.md +├── personas/ # Role-specific guidelines +│ ├── security-expert.md +│ └── ui-designer.md +├── rules/ # Generic rules and patterns +│ ├── api-security.md +│ ├── code-quality.md +│ └── testing-standards.md +├── skills/ # Reusable knowledge packages +│ ├── oauth2-flows/ +│ │ └── SKILL.md +│ ├── python-logging/ +│ │ └── SKILL.md +│ └── react-patterns/ +│ └── SKILL.md +├── examples/ # Annotated examples +│ ├── auth-flow.md +│ └── api-design.md +└── .skills.json # Skills manifest (required/recommended/blocked) +``` + +### Configuration (.skills.json) + +```json +{ + "version": "1.0.0", + "source": "team-ai-directives", + "skills": { + "required": { + "github:vercel-labs/agent-skills/react-best-practices": "^1.2.0", + "local:./skills/oauth2-flows": "*" + }, + "recommended": { + "github:org/skills/python-patterns": "~2.0.0" + }, + "blocked": [ + "github:unsafe/deprecated-skill" + ] + }, + "policy": { + "auto_install_required": true, + "enforce_blocked": true + } +} +``` + +## Layer 1: Script-Based Discovery + +### Context Auto-Discovery (`discover_directives()`) + +**Function:** `scripts/bash/create-new-feature.sh` (`discover_directives()`) + +**Algorithm:** + +1. Extract keywords from feature description +2. Search team-ai-directives for matches in constitutions, personas, rules +3. Rank by keyword overlap and content analysis +4. Output JSON with file paths and search metadata + +**JSON Output:** + +```json +{ + "DISCOVERED_DIRECTIVES": { + "candidates": { + "constitution": "team-ai-directives/constitutions/constitution.md", + "personas": [ + "team-ai-directives/personas/security-expert.md" + ], + "rules": [ + "team-ai-directives/rules/api-security.md", + "team-ai-directives/rules/code-quality.md" + ], + "skills": [], + "examples": [] + }, + "search_metadata": { + "keywords": ["authentication", "security", "oauth"], + "files_searched": 15, + "files_with_matches": 5 + } + } +} +``` + +**PowerShell Equivalent:** `scripts/powershell/discovery-functions.ps1` (`Discover-Directives`) + +### Skills Discovery (`discover_skills()`) + +**Function:** `scripts/bash/create-new-feature.sh` (`discover_skills()`) + +**5-Layer Discovery Algorithm:** + +| Layer | Source | Description | +|-------|--------|-------------| +| 1. Manifest | `.skills.json` | Fetch required/recommended/blocked skill definitions | +| 2. Local | `team-ai-directives/skills/` | Search for SKILL.md files in local directory | +| 3. Cache | `skills-cache/` | Check cached skills with 24h TTL refresh | +| 4. Required | Manifest URLs | Fetch remote skills from manifest URLs | +| 5. Recommended | Keywords | Semantic matching against feature description (future) | + +**TTL Cache Refresh:** + +- Cache marker: `skills-cache/.last_refresh` (Unix timestamp) +- Refresh cycle: 24 hours (86400 seconds) +- Automatic rebuild when timestamp expired + +**JSON Output:** + +```json +{ + "DISCOVERED_SKILLS": { + "candidates": [ + { + "id": "required:oauth2-flows", + "name": "oauth2-flows", + "source": "manifest", + "base_relevance": 1.0 + }, + { + "id": "required:python-logging", + "name": "python-logging", + "source": "local", + "base_relevance": 0.95 + } + ], + "last_refresh": "2026-03-07T15:30:00Z" + } +} +``` + +**PowerShell Equivalent:** `scripts/powershell/discovery-functions.ps1` (`Discover-Skills`) + +## Layer 2: AI-Powered Discovery + +### Template Integration + +#### specify.md Template + +**Location:** After initial context generation (line ~186) + +**Instruction:** AI agents enhance baseline discoveries by adding human-readable explanations. + +```markdown +## AI-Powered Discovery + +AI agent, enhance the discovered directives and skills with concise, relevant explanations (1-2 sentences each). + +### Discovered Directives (AI-Enhanced) + +- **[constitution]**: [Concise explanation of key principles] +- **[personas]**: [Role-specific guidance application] +- **[rules]**: [Specific patterns to follow] +- **[skills]**: [Skill applications to this feature] +- **[examples]**: [Relevant examples to reference] + +### Relevant Skills (AI-Selected) + +- **[skill-name]**@version (confidence: score): [Explanation of relevance] +``` + +#### plan.md Template + +**Location:** Context/Skills Refresh section before implementation (line ~360) + +**Instruction:** AI agents revalidate selections and add technical context. + +```markdown +## AI-Powered Context & Skills Refresh + +Re-evaluate discovered directives and skills. Remove irrelevant items and add new discoveries. + +### Revised Directives List + +[AI agent updates directives based on plan context] + +### Revised Skills List + +[AI agent updates skills based on implementation approach] + +### Confidence Scores & Justifications + +- **[directive/skill-name]**: [confidence_score] - [reasoning] +``` + +### AI Discovery Behavior + +**Conflict Resolution:** + +- Script confidence > AI confidence: Keep script selection +- Script confidence < AI confidence: Update with AI selection +- Missing items only in scripts: Include (AI may have overlooked) +- Missing items only in AI: Include (script baseline may be incomplete) + +**Explanation Style:** + +- Concise: 1-2 sentences max +- Relevant: Tie to specific feature context +- Actionable: Provide clear guidance + +**Fallback Behavior:** + +- If AI fails silently: Fall back to script baseline +- If JSON output malformed: Log warning, use script baseline only + +## Integration with create-new-feature.sh + +### Bash Script Flow + +```bash +#!/usr/bin/env bash + +feature_description="Add user authentication with OAuth2" +team_directives_dir=".specify/team-ai-directives" + +# 1. Discover directives +directives_json=$(discover_directives "$feature_description" "$team_directives_dir") + +# 2. Discover skills +skills_json=$(discover_skills "$feature_description" "$team_directives_dir" ".specify/skills-cache") + +# 3. Output combined payload +jq -n \ + --argjson directives "$directives_json" \ + --argjson skills "$skills_json" \ + '{ + DISCOVERED_DIRECTIVES: $directives.candidates, + DISCOVERED_SKILLS: $skills.candidates, + feature_description: env.FEATURE_DESCRIPTION + }' < team-ai-directives/constitutions/constitution.md + +# Run feature creation +scripts/create-new-feature.sh "Test feature" +# Verify DISCOVERED_DIRECTIVES output +``` + +## Performance Considerations + +### Cache Strategy + +- **Refresh Cycle**: 24 hours reduces repeated large repository scans +- **Marker File**: `.last_refresh` timestamp for age tracking +- **Automatic Cleanup**: Stale entries replaced during refresh + +### Scalability + +- **Grep Search**: Fast for typical team-ai-directives (~100 files) +- **Candidate Limiting**: Max 5 skills configurable via scripts +- **URL Fetching**: Only for manifest URLs (not entire skills.sh registry) + +## Troubleshooting + +### Issue: No Directives/Skills Discovered + +**Solution:** Verify team-ai-directives path exists and contains constitutions/rules/skills directories + +```bash +ls -la ~/.specify/team-ai-directives +# Should show: constitutions/, personas/, rules/, skills/, examples/ +``` + +### Issue: Skills Not Refreshed After Team Changes + +**Solution:** Delete cache marker to force immediate refresh + +```bash +rm ~/.specify/skills-cache/.last_refresh +``` + +### Issue: URL Fetching Fails + +**Solution:** Check network connectivity and manifest URL validity + +```bash +# Test URL accessibility +curl -I https://example.com/skill-url.md +# Should return 200 OK +``` + +## Future Enhancements + +### Skills Semantic Selection (Not Implemented) + +- NLP-based semantic matching against feature description +- Relevance confidence scoring algorithm +- Threshold-based filtering (configurable via config.json) +- Integration with skills.sh registry (optional, based on user preference) + +### Directive Discovery Enhancement (Not Implemented) + +- Constitution rule extraction for automated compliance checking +- Persona-based directive filtering (optional) +- Example-based pattern discovery from annotated examples + +## References + +- Issue #47: Context Auto-Discovery +- Issue #49: Skills Discovery +- Team AI Directives best practices +- Skills Package Manager documentation \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index bc19cb5e49..2dcaad7664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agentic-sdlc-specify-cli" -version = "0.0.115" +version = "0.0.116" description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." requires-python = ">=3.11" dependencies = [ diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 85615a0d96..b99eb7bc90 100755 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -151,8 +151,8 @@ get_highest_from_branches() { check_existing_branches() { local specs_dir="$1" - # Fetch all remotes to get latest branch info (suppress errors if no remotes) - git fetch --all --prune 2>/dev/null || true + # Fetch all remotes to get latest branch info (suppress all output) + git fetch --all --prune >/dev/null 2>&1 || true # Get highest number from ALL branches (not just matching short name) local highest_branch=$(get_highest_from_branches) @@ -441,7 +441,7 @@ discover_skills() { local need_refresh=false if [[ -f "$cache_marker" ]]; then - local last_refresh=$(cat "$cache_marker) + local last_refresh=$(cat "$cache_marker") local age=$((current_timestamp - last_refresh)) if [[ $age -gt $one_day ]]; then need_refresh=true diff --git a/scripts/powershell/discovery-functions.ps1 b/scripts/powershell/discovery-functions.ps1 new file mode 100644 index 0000000000..a0836bc4f5 --- /dev/null +++ b/scripts/powershell/discovery-functions.ps1 @@ -0,0 +1,139 @@ +function Discover-Directives { + param( + [string]$FeatureDescription, + [string]$TeamDirectivesPath + ) + + if (-not (Test-Path $TeamDirectivesPath)) { + $obj = @{ + candidates = @{ + constitution = "" + personas = @() + rules = @() + skills = @() + examples = @() + } + search_metadata = @{ + keywords = @() + files_searched = 0 + files_with_matches = 0 + } + } + return $obj | ConvertTo-Json -Compress + } + + $constitution = "" + if (Test-Path "$TeamDirectivesPath/constitutions/constitution.md") { + $constitution = "$TeamDirectivesPath/constitutions/constitution.md" + } elseif (Test-Path "$TeamDirectivesPath/constitution.md") { + $constitution = "$TeamDirectivesPath/constitution.md" + } + + $obj = @{ + candidates = @{ + constitution = $constitution + personas = @() + rules = @() + skills = @() + examples = @() + } + search_metadata = @{ + keywords = @() + files_searched = 0 + files_with_matches = 0 + } + } + return $obj | ConvertTo-Json -Compress +} + +function Discover-Skills { + param( + [string]$FeatureDescription, + [string]$TeamDirectivesPath, + [string]$SkillsCachePath, + [int]$MaxSkills = 5, + [double]$Threshold = 0.7 + ) + + New-Item -ItemType Directory -Path $SkillsCachePath -Force | Out-Null + + $cacheMarker = "$SkillsCachePath/.last_refresh" + $currentTimestamp = [int][double]::Parse((Get-Date -UFormat %s)) + $oneDay = 86400 + + $needRefresh = $false + if (Test-Path $cacheMarker) { + $lastRefresh = [int](Get-Content $cacheMarker) + $age = $currentTimestamp - $lastRefresh + if ($age -gt $oneDay) { + $needRefresh = $true + } + } else { + $needRefresh = $true + } + + if ($needRefresh -and (Test-Path "$TeamDirectivesPath/skills")) { + Write-Host "[specify] Refreshing skills cache (daily refresh)..." -ForegroundColor Yellow + Copy-Item "$TeamDirectivesPath/skills/*" $SkillsCachePath -Recurse -Force -ErrorAction SilentlyContinue + $currentTimestamp | Out-File $cacheMarker + } + + $requiredSkills = @() + $blockedSkills = @() + + if (Test-Path "$TeamDirectivesPath/.skills.json") { + $manifest = Get-Content "$TeamDirectivesPath/.skills.json" -Raw | ConvertFrom-Json + + if ($manifest.skills.required) { + foreach ($skillId in $manifest.skills.required.PSObject.Properties.Name) { + if ($manifest.skills.required.$skillId.url) { + $skillUrl = $manifest.skills.required.$skillId.url + $cacheDir = Join-Path $SkillsCachePath (Split-Path $skillId -Leaf) + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + try { + Invoke-WebRequest -Uri $skillUrl -OutFile "$cacheDir/SKILL.md" -ErrorAction SilentlyContinue + if ((Test-Path "$cacheDir/SKILL.md") -and ((Get-Item "$cacheDir/SKILL.md").Length -gt 0)) { + $requiredSkills += "required:$skillId" + } + } catch { } + } elseif ($skillId -like "local:*") { + $requiredSkills += "required:$skillId" + } elseif ((Test-Path "$SkillsCachePath/$skillId") -and (Test-Path "$SkillsCachePath/$skillId/SKILL.md")) { + $requiredSkills += "required:$skillId" + } + } + } + + if ($manifest.skills.blocked) { + $blockedSkills = $manifest.skills.blocked + } + } + + $candidates = @() + foreach ($skillId in $requiredSkills) { + if ($blockedSkills -contains $skillId) { continue } + + $skillPath = if ($skillId -like "local:*") { + $TeamDirectivesPath + "\" + ($skillId -replace "local:", "") + } else { + Join-Path $SkillsCachePath $skillId + } + + $skillName = Split-Path $skillPath -Leaf + if ((-not (Test-Path "$skillPath/SKILL.md")) -or ((Get-Item "$skillPath/SKILL.md").Length -eq 0)) { continue } + + $candidates += [PSCustomObject]@{ + id = "required:$skillName" + name = $skillName + source = "manifest" + base_relevance = 1.0 + }] + } + + $candidates = $candidates[0..([Math]::Min($candidates.Count, $MaxSkills) - 1)] + + return @{ + candidates = $candidates + last_refresh = Get-Date -UFormat "%Y-%m-%dT%H:%M:%SZ" + } | ConvertTo-Json -Compress +}