From 4bee205033ed8639b31974886a17b7d51a3dea58 Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 12:37:45 +0800 Subject: [PATCH 01/11] feat: add summarize command for brownfield project analysis Adds 'specify summarize' to analyze existing projects and generate technology stack summaries, making it easier to onboard to unfamiliar codebases or document project architecture. --- README.md | 5 + scripts/bash/generate-project-summary.sh | 255 ++++++++++++++++++ src/specify_cli/__init__.py | 192 +++++++++++++ templates/commands/summarize.md | 326 +++++++++++++++++++++++ templates/project-summary-template.md | 81 ++++++ 5 files changed, 859 insertions(+) create mode 100755 scripts/bash/generate-project-summary.sh create mode 100644 templates/commands/summarize.md create mode 100644 templates/project-summary-template.md diff --git a/README.md b/README.md index 1c7dda215..3e750ae5d 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ The `specify` command supports the following options: | Command | Description | |-------------|----------------------------------------------------------------| | `init` | Initialize a new Specify project from the latest template | +| `summarize` | Generate a comprehensive summary of an existing project's technology stack, architecture, and code conventions | | `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`) | ### `specify init` Arguments & Options @@ -219,6 +220,9 @@ specify init my-project --ai claude --github-token ghp_your_token_here # Check system requirements specify check + +# Analyze existing project (for brownfield/existing codebases) +specify summarize ``` ### Available Slash Commands @@ -243,6 +247,7 @@ Additional commands for enhanced quality and validation: | Command | Description | |----------------------|-----------------------------------------------------------------------| +| `/speckit.summarize` | Generate comprehensive summary of existing project (technology stack, architecture, code conventions) - ideal for brownfield projects | | `/speckit.clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) | | `/speckit.analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) | | `/speckit.checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") | diff --git a/scripts/bash/generate-project-summary.sh b/scripts/bash/generate-project-summary.sh new file mode 100755 index 000000000..fad209247 --- /dev/null +++ b/scripts/bash/generate-project-summary.sh @@ -0,0 +1,255 @@ +#!/usr/bin/env bash +set -e + +# Source common utilities +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Parse command line arguments +JSON_OUTPUT=false + +while [[ $# -gt 0 ]]; do + case $1 in + --json) + JSON_OUTPUT=true + shift + ;; + *) + shift + ;; + esac +done + +# Get repository root +REPO_ROOT=$(get_repo_root) + +# Initialize arrays for discovered data +declare -a TECH_FILES=() +declare -a DIRECTORIES=() +declare -a LANGUAGES=() + +# Detect configuration files for different languages/frameworks +detect_config_files() { + local root="$1" + + # JavaScript/TypeScript + [[ -f "$root/package.json" ]] && TECH_FILES+=("package.json") && LANGUAGES+=("JavaScript/TypeScript") + [[ -f "$root/tsconfig.json" ]] && TECH_FILES+=("tsconfig.json") + [[ -f "$root/yarn.lock" ]] && TECH_FILES+=("yarn.lock") + [[ -f "$root/pnpm-lock.yaml" ]] && TECH_FILES+=("pnpm-lock.yaml") + [[ -f "$root/bun.lockb" ]] && TECH_FILES+=("bun.lockb") + + # Python + [[ -f "$root/requirements.txt" ]] && TECH_FILES+=("requirements.txt") && LANGUAGES+=("Python") + [[ -f "$root/pyproject.toml" ]] && TECH_FILES+=("pyproject.toml") && LANGUAGES+=("Python") + [[ -f "$root/setup.py" ]] && TECH_FILES+=("setup.py") && LANGUAGES+=("Python") + [[ -f "$root/Pipfile" ]] && TECH_FILES+=("Pipfile") && LANGUAGES+=("Python") + [[ -f "$root/poetry.lock" ]] && TECH_FILES+=("poetry.lock") + + # Go + [[ -f "$root/go.mod" ]] && TECH_FILES+=("go.mod") && LANGUAGES+=("Go") + [[ -f "$root/go.sum" ]] && TECH_FILES+=("go.sum") + + # Rust + [[ -f "$root/Cargo.toml" ]] && TECH_FILES+=("Cargo.toml") && LANGUAGES+=("Rust") + [[ -f "$root/Cargo.lock" ]] && TECH_FILES+=("Cargo.lock") + + # Java + [[ -f "$root/pom.xml" ]] && TECH_FILES+=("pom.xml") && LANGUAGES+=("Java") + [[ -f "$root/build.gradle" ]] && TECH_FILES+=("build.gradle") && LANGUAGES+=("Java/Kotlin") + [[ -f "$root/build.gradle.kts" ]] && TECH_FILES+=("build.gradle.kts") && LANGUAGES+=("Kotlin") + + # Ruby + [[ -f "$root/Gemfile" ]] && TECH_FILES+=("Gemfile") && LANGUAGES+=("Ruby") + [[ -f "$root/Gemfile.lock" ]] && TECH_FILES+=("Gemfile.lock") + + # PHP + [[ -f "$root/composer.json" ]] && TECH_FILES+=("composer.json") && LANGUAGES+=("PHP") + [[ -f "$root/composer.lock" ]] && TECH_FILES+=("composer.lock") + + # C#/.NET + find "$root" -maxdepth 2 -name "*.csproj" -o -name "*.fsproj" -o -name "*.vbproj" | while read -r proj; do + TECH_FILES+=("$(basename "$proj")") + LANGUAGES+=("C#/.NET") + done + + # Swift + [[ -f "$root/Package.swift" ]] && TECH_FILES+=("Package.swift") && LANGUAGES+=("Swift") + + # Elixir + [[ -f "$root/mix.exs" ]] && TECH_FILES+=("mix.exs") && LANGUAGES+=("Elixir") + + # C/C++ + [[ -f "$root/CMakeLists.txt" ]] && TECH_FILES+=("CMakeLists.txt") && LANGUAGES+=("C/C++") + [[ -f "$root/Makefile" ]] && TECH_FILES+=("Makefile") + + # Code style/config files + [[ -f "$root/.editorconfig" ]] && TECH_FILES+=(".editorconfig") + [[ -f "$root/.prettierrc" ]] && TECH_FILES+=(".prettierrc") + [[ -f "$root/.prettierrc.json" ]] && TECH_FILES+=(".prettierrc.json") + [[ -f "$root/.eslintrc" ]] && TECH_FILES+=(".eslintrc") + [[ -f "$root/.eslintrc.json" ]] && TECH_FILES+=(".eslintrc.json") + [[ -f "$root/rustfmt.toml" ]] && TECH_FILES+=("rustfmt.toml") +} + +# Detect key directories +detect_directories() { + local root="$1" + + # Source directories + [[ -d "$root/src" ]] && DIRECTORIES+=("src") + [[ -d "$root/lib" ]] && DIRECTORIES+=("lib") + [[ -d "$root/app" ]] && DIRECTORIES+=("app") + [[ -d "$root/pkg" ]] && DIRECTORIES+=("pkg") + [[ -d "$root/internal" ]] && DIRECTORIES+=("internal") + [[ -d "$root/cmd" ]] && DIRECTORIES+=("cmd") + + # Test directories + [[ -d "$root/test" ]] && DIRECTORIES+=("test") + [[ -d "$root/tests" ]] && DIRECTORIES+=("tests") + [[ -d "$root/__tests__" ]] && DIRECTORIES+=("__tests__") + [[ -d "$root/spec" ]] && DIRECTORIES+=("spec") + + # Documentation + [[ -d "$root/docs" ]] && DIRECTORIES+=("docs") + [[ -d "$root/documentation" ]] && DIRECTORIES+=("documentation") + + # Configuration + [[ -d "$root/config" ]] && DIRECTORIES+=("config") + [[ -d "$root/conf" ]] && DIRECTORIES+=("conf") + [[ -d "$root/settings" ]] && DIRECTORIES+=("settings") + + # Frontend/Backend split + [[ -d "$root/frontend" ]] && DIRECTORIES+=("frontend") + [[ -d "$root/client" ]] && DIRECTORIES+=("client") + [[ -d "$root/web" ]] && DIRECTORIES+=("web") + [[ -d "$root/backend" ]] && DIRECTORIES+=("backend") + [[ -d "$root/server" ]] && DIRECTORIES+=("server") + [[ -d "$root/api" ]] && DIRECTORIES+=("api") + + # Database + [[ -d "$root/migrations" ]] && DIRECTORIES+=("migrations") + [[ -d "$root/models" ]] && DIRECTORIES+=("models") + [[ -d "$root/schemas" ]] && DIRECTORIES+=("schemas") + + # Monorepo + [[ -d "$root/packages" ]] && DIRECTORIES+=("packages") + [[ -d "$root/apps" ]] && DIRECTORIES+=("apps") + [[ -d "$root/services" ]] && DIRECTORIES+=("services") + + # Public/Static + [[ -d "$root/public" ]] && DIRECTORIES+=("public") + [[ -d "$root/static" ]] && DIRECTORIES+=("static") + [[ -d "$root/assets" ]] && DIRECTORIES+=("assets") + + # Build/Dist + [[ -d "$root/build" ]] && DIRECTORIES+=("build") + [[ -d "$root/dist" ]] && DIRECTORIES+=("dist") + [[ -d "$root/target" ]] && DIRECTORIES+=("target") +} + +# Detect project type +detect_project_type() { + local type="unknown" + + # Monorepo indicators + if [[ -d "$REPO_ROOT/packages" ]] || [[ -d "$REPO_ROOT/apps" ]] || [[ -d "$REPO_ROOT/services" ]]; then + type="monorepo" + # Frontend + Backend split + elif [[ -d "$REPO_ROOT/frontend" ]] && [[ -d "$REPO_ROOT/backend" ]]; then + type="fullstack-split" + elif [[ -d "$REPO_ROOT/client" ]] && [[ -d "$REPO_ROOT/server" ]]; then + type="fullstack-split" + # Web application + elif [[ -f "$REPO_ROOT/package.json" ]] && grep -q "react\|vue\|angular\|svelte\|next\|nuxt" "$REPO_ROOT/package.json" 2>/dev/null; then + type="web-frontend" + # API/Backend + elif [[ -d "$REPO_ROOT/api" ]] || [[ -d "$REPO_ROOT/server" ]]; then + type="backend-api" + # CLI tool + elif [[ -d "$REPO_ROOT/cmd" ]] || ( [[ -f "$REPO_ROOT/package.json" ]] && grep -q "\"bin\":" "$REPO_ROOT/package.json" 2>/dev/null ); then + type="cli-tool" + # Library + elif [[ -d "$REPO_ROOT/lib" ]] || ( [[ -f "$REPO_ROOT/Cargo.toml" ]] && grep -q "\[lib\]" "$REPO_ROOT/Cargo.toml" 2>/dev/null ); then + type="library" + # Generic application + elif [[ -d "$REPO_ROOT/src" ]]; then + type="application" + fi + + echo "$type" +} + +# Run detection +detect_config_files "$REPO_ROOT" +detect_directories "$REPO_ROOT" +PROJECT_TYPE=$(detect_project_type) + +# Deduplicate languages array +if [ ${#LANGUAGES[@]} -eq 0 ]; then + UNIQUE_LANGUAGES=() +else + UNIQUE_LANGUAGES=($(printf '%s\n' "${LANGUAGES[@]}" | sort -u)) +fi + +# Output as JSON if requested +if [ "$JSON_OUTPUT" = true ]; then + # Convert arrays to JSON arrays (handle empty arrays) + if [ ${#TECH_FILES[@]} -eq 0 ]; then + tech_files_json="[]" + else + tech_files_json=$(printf '%s\n' "${TECH_FILES[@]}" | jq -R . | jq -s .) + fi + + if [ ${#DIRECTORIES[@]} -eq 0 ]; then + directories_json="[]" + else + directories_json=$(printf '%s\n' "${DIRECTORIES[@]}" | jq -R . | jq -s .) + fi + + if [ ${#UNIQUE_LANGUAGES[@]} -eq 0 ]; then + languages_json="[]" + else + languages_json=$(printf '%s\n' "${UNIQUE_LANGUAGES[@]}" | jq -R . | jq -s .) + fi + + # Output JSON + jq -n \ + --arg repo_root "$REPO_ROOT" \ + --arg project_type "$PROJECT_TYPE" \ + --argjson tech_files "$tech_files_json" \ + --argjson directories "$directories_json" \ + --argjson languages "$languages_json" \ + '{ + REPO_ROOT: $repo_root, + PROJECT_TYPE: $project_type, + TECH_FILES: $tech_files, + DIRECTORIES: $directories, + LANGUAGES: $languages + }' +else + # Human-readable output + echo "Repository Root: $REPO_ROOT" + echo "Project Type: $PROJECT_TYPE" + echo "" + echo "Languages:" + if [ ${#UNIQUE_LANGUAGES[@]} -gt 0 ]; then + printf ' - %s\n' "${UNIQUE_LANGUAGES[@]}" + else + echo " (none detected)" + fi + echo "" + echo "Configuration Files:" + if [ ${#TECH_FILES[@]} -gt 0 ]; then + printf ' - %s\n' "${TECH_FILES[@]}" + else + echo " (none detected)" + fi + echo "" + echo "Key Directories:" + if [ ${#DIRECTORIES[@]} -gt 0 ]; then + printf ' - %s\n' "${DIRECTORIES[@]}" + else + echo " (none detected)" + fi +fi diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index a33a1c61a..563f2469e 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1160,6 +1160,198 @@ def init( console.print() console.print(enhancements_panel) +@app.command() +def summarize(): + """Generate a comprehensive summary of an existing project.""" + show_banner() + console.print("[bold]Analyzing project...[/bold]\n") + + tracker = StepTracker("Project Analysis") + + # Find repository root + try: + tracker.add("find-root", "Locate repository root") + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=False + ) + if result.returncode == 0: + repo_root = Path(result.stdout.strip()) + tracker.complete("find-root", str(repo_root)) + else: + # Fallback to current directory + repo_root = Path.cwd() + # Check for .specify directory + while repo_root != repo_root.parent: + if (repo_root / ".specify").exists(): + break + repo_root = repo_root.parent + else: + repo_root = Path.cwd() + tracker.complete("find-root", str(repo_root)) + except Exception as e: + tracker.error("find-root", str(e)) + console.print(tracker.render()) + raise typer.Exit(1) + + # Determine script type (prefer bash on Unix-like, PowerShell on Windows) + is_windows = sys.platform == "win32" + script_ext = "ps1" if is_windows else "sh" + script_dir = "powershell" if is_windows else "bash" + + # Find script path (could be in installed location or development location) + script_name = f"generate-project-summary.{script_ext}" + + # Try to find script in various locations + possible_script_paths = [ + repo_root / ".specify" / "scripts" / script_name, + repo_root / "scripts" / script_dir / script_name, + Path(__file__).parent.parent.parent / "scripts" / script_dir / script_name, + ] + + script_path = None + for path in possible_script_paths: + if path.exists(): + script_path = path + break + + if not script_path: + tracker.error("find-script", f"Could not find {script_name}") + console.print(tracker.render()) + console.print(f"\n[red]Error: Analysis script not found. Tried:[/red]") + for path in possible_script_paths: + console.print(f" - {path}") + raise typer.Exit(1) + + tracker.add("run-script", f"Run analysis script ({script_name})") + + # Run the script with --json flag + try: + if is_windows: + cmd = ["pwsh", "-File", str(script_path), "-Json"] + else: + cmd = ["/bin/bash", str(script_path), "--json"] + + result = subprocess.run( + cmd, + cwd=str(repo_root), + capture_output=True, + text=True, + check=True + ) + + # Parse JSON output + data = json.loads(result.stdout) + tracker.complete("run-script", f"Found {len(data.get('LANGUAGES', []))} language(s)") + + except subprocess.CalledProcessError as e: + tracker.error("run-script", f"Script failed: {e.stderr}") + console.print(tracker.render()) + raise typer.Exit(1) + except json.JSONDecodeError as e: + tracker.error("run-script", f"Invalid JSON output: {e}") + console.print(tracker.render()) + raise typer.Exit(1) + + console.print(tracker.render()) + + # Display results + console.print("\n[bold cyan]📊 Project Summary[/bold cyan]\n") + + # Project type + console.print(f"[bold]Project Type:[/bold] {data.get('PROJECT_TYPE', 'unknown')}") + console.print(f"[bold]Repository:[/bold] {data.get('REPO_ROOT', '')}\n") + + # Languages + languages = data.get('LANGUAGES', []) + if languages: + console.print("[bold]Languages:[/bold]") + for lang in languages: + console.print(f" • {lang}") + console.print() + + # Configuration files + tech_files = data.get('TECH_FILES', []) + if tech_files: + console.print(f"[bold]Configuration Files:[/bold] ({len(tech_files)} found)") + # Show first 10, then truncate + for file in tech_files[:10]: + console.print(f" • {file}") + if len(tech_files) > 10: + console.print(f" ... and {len(tech_files) - 10} more") + console.print() + + # Directories + directories = data.get('DIRECTORIES', []) + if directories: + console.print(f"[bold]Key Directories:[/bold] ({len(directories)} found)") + for dir in directories[:15]: + console.print(f" • {dir}/") + if len(directories) > 15: + console.print(f" ... and {len(directories) - 15} more") + console.print() + + # Save summary + memory_dir = repo_root / ".specify" / "memory" + summary_file = memory_dir / "project-summary.md" + + # Check if .specify exists + if not (repo_root / ".specify").exists(): + console.print("[yellow]⚠️ .specify directory not found.[/yellow]") + console.print("[dim]Run 'specify init --here --ai ' first to initialize spec-kit.[/dim]\n") + else: + # Ensure memory directory exists + memory_dir.mkdir(parents=True, exist_ok=True) + + # Generate summary content (basic version for now) + from datetime import datetime + summary_content = f"""# Project Summary + +**Generated**: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} +**Repository**: {data.get('REPO_ROOT', '')} + +## Technology Stack + +### Languages +{chr(10).join(f'- {lang}' for lang in languages) if languages else '- (none detected)'} + +### Configuration Files +{chr(10).join(f'- `{file}`' for file in tech_files[:20]) if tech_files else '- (none detected)'} +{f'- ... and {len(tech_files) - 20} more' if len(tech_files) > 20 else ''} + +## Project Structure + +### Architecture Pattern +{data.get('PROJECT_TYPE', 'unknown')} + +### Key Directories +{chr(10).join(f'- `{dir}/`' for dir in directories) if directories else '- (none detected)'} + +## Notes + +This is a basic summary generated by `specify summarize`. +For a more detailed analysis, use the `/speckit.summarize` command in your AI assistant. + +--- + +*Generated by `specify summarize` - re-run this command to update the summary.* +""" + + summary_file.write_text(summary_content, encoding="utf-8") + console.print(f"[bold green]✅ Summary saved to:[/bold green] {summary_file}\n") + + # Next steps + console.print("[bold]💡 Next Steps:[/bold]") + if not (repo_root / ".specify").exists(): + console.print(" 1. Run [cyan]specify init --here --ai [/cyan] to initialize spec-kit") + else: + console.print(" 1. Review the generated summary") + console.print(" 2. Run [cyan]/speckit.constitution[/cyan] in your AI assistant to define project principles") + console.print(" 3. Start creating feature specs with [cyan]/speckit.specify[/cyan]") + + @app.command() def check(): """Check that all required tools are installed.""" diff --git a/templates/commands/summarize.md b/templates/commands/summarize.md new file mode 100644 index 000000000..bbcb2ef64 --- /dev/null +++ b/templates/commands/summarize.md @@ -0,0 +1,326 @@ +--- +description: Generate a comprehensive summary of an existing project's technology stack, architecture, and code conventions +scripts: + sh: scripts/bash/generate-project-summary.sh --json + ps: scripts/powershell/generate-project-summary.ps1 -Json +--- + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Goal + +Analyze an existing codebase to extract its technology stack, project structure, code conventions, and testing setup. This command helps bootstrap spec-kit usage in brownfield/existing projects by automatically discovering project characteristics. + +## Operating Constraints + +**STRICTLY READ-ONLY**: Do **not** modify any files. Only analyze existing code and generate a summary document. + +**Progressive Analysis**: Start with configuration files, then analyze directory structure, and finally sample code files for conventions. + +**Smart Detection**: Adapt analysis depth based on project type and available indicators. + +## Execution Steps + +### 1. Initialize Project Scan + +Run `{SCRIPT}` from repo root and parse JSON output for: + +- REPO_ROOT: Project root directory +- TECH_FILES: Discovered configuration files (package.json, pyproject.toml, etc.) +- DIRECTORIES: Key directories found (src/, tests/, etc.) +- PROJECT_TYPE: Detected project type (web, cli, library, monorepo, etc.) + +For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). + +### 2. Analyze Technology Stack + +**Scan for language and framework indicators:** + +Load and analyze configuration files returned by the script: + +**JavaScript/TypeScript:** +- package.json: Extract dependencies, devDependencies, scripts +- tsconfig.json: TypeScript configuration +- Detect frameworks: React, Vue, Angular, Next.js, Express, etc. + +**Python:** +- requirements.txt, pyproject.toml, setup.py, Pipfile +- Detect frameworks: Django, Flask, FastAPI, etc. + +**Go:** +- go.mod, go.sum +- Detect frameworks: Gin, Echo, Chi, etc. + +**Rust:** +- Cargo.toml +- Detect frameworks: Actix, Rocket, Axum, etc. + +**Java:** +- pom.xml, build.gradle +- Detect frameworks: Spring Boot, Quarkus, etc. + +**Other languages:** +- Ruby: Gemfile +- PHP: composer.json +- C#: *.csproj + +**Output consolidated technology stack:** +- Primary language(s) and version(s) +- Main framework(s) +- Key dependencies (top 5-10 most significant) +- Package manager +- Build tool(s) + +### 3. Analyze Project Structure + +**Map directory structure:** + +Scan for common patterns: +- Source code: src/, lib/, app/, pkg/, internal/ +- Tests: test/, tests/, __tests__/, spec/ +- Documentation: docs/, documentation/ +- Configuration: config/, conf/, settings/ +- Frontend: frontend/, client/, web/, public/ +- Backend: backend/, server/, api/ +- Database: migrations/, models/, schemas/ + +**Identify architecture pattern:** +- Monorepo (presence of packages/, apps/, services/) +- Microservices (multiple service directories) +- Frontend + Backend split +- MVC/MVVM (models/, views/, controllers/) +- Clean Architecture (domain/, application/, infrastructure/) +- Modular monolith (modules/, features/) + +**Output:** +- Directory tree (top 2 levels) +- Identified architecture pattern +- Key directories with purpose + +### 4. Analyze Code Conventions + +**Sample representative files** (read 3-5 files from each category): + +**Naming conventions:** +- File naming: camelCase, PascalCase, snake_case, kebab-case +- Variable naming: camelCase, snake_case +- Function naming: camelCase, snake_case, PascalCase +- Class naming: PascalCase, snake_case +- Constant naming: UPPER_CASE, camelCase + +**Code style:** +- Indentation: tabs vs spaces, width (2, 4) +- Line length limits +- Brace style (K&R, Allman, etc.) +- Import/require organization + +**Documentation style:** +- JSDoc, docstrings, rustdoc, GoDoc +- Comment density and patterns +- README structure + +**Look for configuration files:** +- .editorconfig +- .prettierrc, prettier.config.js +- .eslintrc, eslint.config.js +- pyproject.toml [tool.black], [tool.ruff] +- rustfmt.toml +- .clang-format + +**Output:** +- Naming convention summary +- Indentation style +- Code style guide (if configured) +- Documentation patterns + +### 5. Analyze Testing Setup + +**Detect test frameworks:** + +**JavaScript/TypeScript:** +- Jest, Vitest, Mocha, Jasmine, AVA +- Testing Library, Enzyme +- Playwright, Cypress, Puppeteer + +**Python:** +- pytest, unittest, nose +- Coverage.py + +**Go:** +- testing (built-in) +- testify + +**Rust:** +- cargo test (built-in) + +**Java:** +- JUnit, TestNG +- Mockito + +**Look for:** +- Test configuration files +- Test scripts in package.json +- CI/CD configuration (.github/workflows/, .gitlab-ci.yml, etc.) +- Coverage reports configuration + +**Output:** +- Test framework(s) +- Test directory structure +- Test coverage setup (if configured) +- CI/CD integration (if present) + +### 6. Generate Project Summary + +Create a structured summary document at `/memory/project-summary.md`: + +```markdown +# Project Summary + +**Generated**: [CURRENT_DATE] +**Repository**: [REPO_ROOT] + +## Technology Stack + +### Languages +- [LANGUAGE]: [VERSION] + +### Frameworks +- [FRAMEWORK_NAME]: [VERSION] + +### Key Dependencies +- [DEPENDENCY_1]: [VERSION] - [PURPOSE] +- [DEPENDENCY_2]: [VERSION] - [PURPOSE] +... + +### Package Manager +- [PACKAGE_MANAGER] + +### Build Tools +- [BUILD_TOOL] + +## Project Structure + +### Architecture Pattern +[DETECTED_PATTERN] + +### Directory Structure +``` +[DIRECTORY_TREE] +``` + +### Key Directories +- `[DIRECTORY]`: [PURPOSE] +... + +## Code Conventions + +### Naming Conventions +- Files: [FILE_NAMING] +- Variables: [VARIABLE_NAMING] +- Functions: [FUNCTION_NAMING] +- Classes: [CLASS_NAMING] +- Constants: [CONSTANT_NAMING] + +### Code Style +- Indentation: [TABS_OR_SPACES] ([WIDTH]) +- Line length: [MAX_LENGTH] characters +- Style guide: [STYLE_GUIDE_IF_CONFIGURED] + +### Documentation +- Format: [DOC_FORMAT] +- Pattern: [DOC_PATTERN] + +## Testing + +### Test Framework +- [TEST_FRAMEWORK] + +### Test Structure +- Test directory: `[TEST_DIR]` +- Test pattern: [TEST_FILE_PATTERN] + +### Coverage +- Tool: [COVERAGE_TOOL] +- Configuration: [COVERAGE_CONFIG_IF_PRESENT] + +### CI/CD +- Platform: [CI_PLATFORM] +- Configuration: `[CI_CONFIG_FILE]` + +## Notes + +[ANY_ADDITIONAL_OBSERVATIONS] + +--- + +*This summary was generated by `specify summarize`. Update this file as the project evolves.* +``` + +### 7. Output Results + +Display a summary to the user: + +1. **Technology Stack Overview**: Brief summary of detected languages and frameworks +2. **Project Type**: Identified architecture pattern +3. **File Location**: Path to generated summary (`.specify/memory/project-summary.md`) +4. **Next Steps**: Suggest using `/speckit.constitution` to define project principles based on discovered conventions + +**Example output:** + +``` +✅ Project analysis complete! + +📊 Summary: + • Language: TypeScript 5.x + • Framework: Next.js 14.x + • Architecture: Frontend + Backend (monorepo) + • Test Framework: Jest + Playwright + +📄 Detailed summary saved to: + .specify/memory/project-summary.md + +💡 Next steps: + 1. Review the generated summary + 2. Run /speckit.constitution to define project principles + 3. Start creating feature specs with /speckit.specify +``` + +## Operating Principles + +### Analysis Strategy + +- **Configuration first**: Start with package managers and build tools +- **Pattern recognition**: Use heuristics to identify common frameworks and patterns +- **Sampling over exhaustive**: Sample representative files rather than reading entire codebase +- **Graceful degradation**: Provide partial summary if some aspects can't be detected + +### Quality Guidelines + +- **Accuracy over completeness**: Only report what can be confidently detected +- **Evidence-based**: Cite specific files as evidence for conclusions +- **Version awareness**: Include version numbers where available +- **Avoid assumptions**: Mark uncertain detections as "likely" or "possible" + +### Context Efficiency + +- **Minimal file reads**: Target specific configuration and sample files +- **Progressive disclosure**: Analyze in stages (config → structure → conventions → tests) +- **Token-efficient**: Summarize findings concisely +- **Reusable data**: Store summary for future commands to reference + +## Error Handling + +- **No .specify directory**: Initialize it first by suggesting `specify init --here --ai [agent]` +- **Unknown project type**: Still generate summary with available information +- **Multiple languages**: List all detected languages with primary language first +- **Conflicting conventions**: Note variations found in different parts of codebase + +## Context + +{ARGS} diff --git a/templates/project-summary-template.md b/templates/project-summary-template.md new file mode 100644 index 000000000..75b05d6aa --- /dev/null +++ b/templates/project-summary-template.md @@ -0,0 +1,81 @@ +# Project Summary + +**Generated**: [CURRENT_DATE] +**Repository**: [REPO_ROOT] + +## Technology Stack + +### Languages +- [LANGUAGE]: [VERSION] + +### Frameworks +- [FRAMEWORK_NAME]: [VERSION] + +### Key Dependencies +- [DEPENDENCY_1]: [VERSION] - [PURPOSE] +- [DEPENDENCY_2]: [VERSION] - [PURPOSE] +- [DEPENDENCY_3]: [VERSION] - [PURPOSE] + +### Package Manager +- [PACKAGE_MANAGER] + +### Build Tools +- [BUILD_TOOL] + +## Project Structure + +### Architecture Pattern +[DETECTED_PATTERN] + +### Directory Structure +``` +[DIRECTORY_TREE] +``` + +### Key Directories +- `[DIRECTORY_1]`: [PURPOSE] +- `[DIRECTORY_2]`: [PURPOSE] +- `[DIRECTORY_3]`: [PURPOSE] + +## Code Conventions + +### Naming Conventions +- Files: [FILE_NAMING] +- Variables: [VARIABLE_NAMING] +- Functions: [FUNCTION_NAMING] +- Classes: [CLASS_NAMING] +- Constants: [CONSTANT_NAMING] + +### Code Style +- Indentation: [TABS_OR_SPACES] ([WIDTH] spaces) +- Line length: [MAX_LENGTH] characters +- Style guide: [STYLE_GUIDE_IF_CONFIGURED] + +### Documentation +- Format: [DOC_FORMAT] +- Pattern: [DOC_PATTERN] + +## Testing + +### Test Framework +- [TEST_FRAMEWORK] + +### Test Structure +- Test directory: `[TEST_DIR]` +- Test pattern: [TEST_FILE_PATTERN] + +### Coverage +- Tool: [COVERAGE_TOOL] +- Configuration: [COVERAGE_CONFIG_IF_PRESENT] + +### CI/CD +- Platform: [CI_PLATFORM] +- Configuration: `[CI_CONFIG_FILE]` + +## Notes + +[ANY_ADDITIONAL_OBSERVATIONS] + +--- + +*This summary was generated by `specify summarize`. Update this file as the project evolves or re-run the command to regenerate.* From 73c7a7685793306c10c3fc2621566d75cc49183c Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 12:54:15 +0800 Subject: [PATCH 02/11] fix: formatting improvements for summarize command --- .../powershell/generate-project-summary.ps1 | 309 ++++++++++++++++++ src/specify_cli/__init__.py | 2 +- templates/commands/summarize.md | 2 +- templates/project-summary-template.md | 2 +- 4 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 scripts/powershell/generate-project-summary.ps1 diff --git a/scripts/powershell/generate-project-summary.ps1 b/scripts/powershell/generate-project-summary.ps1 new file mode 100644 index 000000000..160bddd35 --- /dev/null +++ b/scripts/powershell/generate-project-summary.ps1 @@ -0,0 +1,309 @@ +#!/usr/bin/env pwsh +# Generate project summary by analyzing existing codebase +[CmdletBinding()] +param( + [switch]$Json, + [switch]$Help +) +$ErrorActionPreference = 'Stop' + +# Show help if requested +if ($Help) { + Write-Host "Usage: ./generate-project-summary.ps1 [-Json]" + Write-Host "" + Write-Host "Options:" + Write-Host " -Json Output in JSON format" + Write-Host " -Help Show this help message" + exit 0 +} + +# Find repository root +function Find-RepositoryRoot { + param( + [string]$StartDir = $PWD, + [string[]]$Markers = @('.git', '.specify') + ) + $current = Resolve-Path $StartDir + while ($true) { + foreach ($marker in $Markers) { + if (Test-Path (Join-Path $current $marker)) { + return $current.Path + } + } + $parent = Split-Path $current -Parent + if ($parent -eq $current) { + # Reached filesystem root without finding markers + return $null + } + $current = $parent + } +} + +$repoRoot = Find-RepositoryRoot +if (-not $repoRoot) { + Write-Error "Could not find repository root (no .git or .specify directory found)" + exit 1 +} + +# Initialize arrays +$techFiles = @() +$directories = @() +$languages = @() + +# Detect configuration files for different languages/frameworks +function Detect-ConfigFiles { + param([string]$Root) + + $script:techFiles = @() + $script:languages = @() + + # JavaScript/TypeScript + if (Test-Path "$Root/package.json") { + $script:techFiles += "package.json" + $script:languages += "JavaScript/TypeScript" + } + if (Test-Path "$Root/tsconfig.json") { $script:techFiles += "tsconfig.json" } + if (Test-Path "$Root/yarn.lock") { $script:techFiles += "yarn.lock" } + if (Test-Path "$Root/pnpm-lock.yaml") { $script:techFiles += "pnpm-lock.yaml" } + if (Test-Path "$Root/bun.lockb") { $script:techFiles += "bun.lockb" } + + # Python + if (Test-Path "$Root/requirements.txt") { + $script:techFiles += "requirements.txt" + $script:languages += "Python" + } + if (Test-Path "$Root/pyproject.toml") { + $script:techFiles += "pyproject.toml" + $script:languages += "Python" + } + if (Test-Path "$Root/setup.py") { + $script:techFiles += "setup.py" + $script:languages += "Python" + } + if (Test-Path "$Root/Pipfile") { + $script:techFiles += "Pipfile" + $script:languages += "Python" + } + if (Test-Path "$Root/poetry.lock") { $script:techFiles += "poetry.lock" } + + # Go + if (Test-Path "$Root/go.mod") { + $script:techFiles += "go.mod" + $script:languages += "Go" + } + if (Test-Path "$Root/go.sum") { $script:techFiles += "go.sum" } + + # Rust + if (Test-Path "$Root/Cargo.toml") { + $script:techFiles += "Cargo.toml" + $script:languages += "Rust" + } + if (Test-Path "$Root/Cargo.lock") { $script:techFiles += "Cargo.lock" } + + # Java + if (Test-Path "$Root/pom.xml") { + $script:techFiles += "pom.xml" + $script:languages += "Java" + } + if (Test-Path "$Root/build.gradle") { + $script:techFiles += "build.gradle" + $script:languages += "Java/Kotlin" + } + if (Test-Path "$Root/build.gradle.kts") { + $script:techFiles += "build.gradle.kts" + $script:languages += "Kotlin" + } + + # Ruby + if (Test-Path "$Root/Gemfile") { + $script:techFiles += "Gemfile" + $script:languages += "Ruby" + } + if (Test-Path "$Root/Gemfile.lock") { $script:techFiles += "Gemfile.lock" } + + # PHP + if (Test-Path "$Root/composer.json") { + $script:techFiles += "composer.json" + $script:languages += "PHP" + } + if (Test-Path "$Root/composer.lock") { $script:techFiles += "composer.lock" } + + # C#/.NET + $csprojFiles = Get-ChildItem -Path $Root -Filter "*.csproj" -File -ErrorAction SilentlyContinue + $fsprojFiles = Get-ChildItem -Path $Root -Filter "*.fsproj" -File -ErrorAction SilentlyContinue + $vbprojFiles = Get-ChildItem -Path $Root -Filter "*.vbproj" -File -ErrorAction SilentlyContinue + + if ($csprojFiles -or $fsprojFiles -or $vbprojFiles) { + foreach ($proj in ($csprojFiles + $fsprojFiles + $vbprojFiles)) { + $script:techFiles += $proj.Name + } + $script:languages += "C#/.NET" + } + + # Swift + if (Test-Path "$Root/Package.swift") { + $script:techFiles += "Package.swift" + $script:languages += "Swift" + } + + # Elixir + if (Test-Path "$Root/mix.exs") { + $script:techFiles += "mix.exs" + $script:languages += "Elixir" + } + + # C/C++ + if (Test-Path "$Root/CMakeLists.txt") { + $script:techFiles += "CMakeLists.txt" + $script:languages += "C/C++" + } + if (Test-Path "$Root/Makefile") { $script:techFiles += "Makefile" } + + # Code style/config files + if (Test-Path "$Root/.editorconfig") { $script:techFiles += ".editorconfig" } + if (Test-Path "$Root/.prettierrc") { $script:techFiles += ".prettierrc" } + if (Test-Path "$Root/.prettierrc.json") { $script:techFiles += ".prettierrc.json" } + if (Test-Path "$Root/.eslintrc") { $script:techFiles += ".eslintrc" } + if (Test-Path "$Root/.eslintrc.json") { $script:techFiles += ".eslintrc.json" } + if (Test-Path "$Root/rustfmt.toml") { $script:techFiles += "rustfmt.toml" } +} + +# Detect key directories +function Detect-Directories { + param([string]$Root) + + $script:directories = @() + + # Source directories + if (Test-Path "$Root/src") { $script:directories += "src" } + if (Test-Path "$Root/lib") { $script:directories += "lib" } + if (Test-Path "$Root/app") { $script:directories += "app" } + if (Test-Path "$Root/pkg") { $script:directories += "pkg" } + if (Test-Path "$Root/internal") { $script:directories += "internal" } + if (Test-Path "$Root/cmd") { $script:directories += "cmd" } + + # Test directories + if (Test-Path "$Root/test") { $script:directories += "test" } + if (Test-Path "$Root/tests") { $script:directories += "tests" } + if (Test-Path "$Root/__tests__") { $script:directories += "__tests__" } + if (Test-Path "$Root/spec") { $script:directories += "spec" } + + # Documentation + if (Test-Path "$Root/docs") { $script:directories += "docs" } + if (Test-Path "$Root/documentation") { $script:directories += "documentation" } + + # Configuration + if (Test-Path "$Root/config") { $script:directories += "config" } + if (Test-Path "$Root/conf") { $script:directories += "conf" } + if (Test-Path "$Root/settings") { $script:directories += "settings" } + + # Frontend/Backend split + if (Test-Path "$Root/frontend") { $script:directories += "frontend" } + if (Test-Path "$Root/client") { $script:directories += "client" } + if (Test-Path "$Root/web") { $script:directories += "web" } + if (Test-Path "$Root/backend") { $script:directories += "backend" } + if (Test-Path "$Root/server") { $script:directories += "server" } + if (Test-Path "$Root/api") { $script:directories += "api" } + + # Database + if (Test-Path "$Root/migrations") { $script:directories += "migrations" } + if (Test-Path "$Root/models") { $script:directories += "models" } + if (Test-Path "$Root/schemas") { $script:directories += "schemas" } + + # Monorepo + if (Test-Path "$Root/packages") { $script:directories += "packages" } + if (Test-Path "$Root/apps") { $script:directories += "apps" } + if (Test-Path "$Root/services") { $script:directories += "services" } + + # Public/Static + if (Test-Path "$Root/public") { $script:directories += "public" } + if (Test-Path "$Root/static") { $script:directories += "static" } + if (Test-Path "$Root/assets") { $script:directories += "assets" } + + # Build/Dist + if (Test-Path "$Root/build") { $script:directories += "build" } + if (Test-Path "$Root/dist") { $script:directories += "dist" } + if (Test-Path "$Root/target") { $script:directories += "target" } +} + +# Detect project type +function Detect-ProjectType { + param([string]$Root) + + $type = "unknown" + + # Monorepo indicators + if ((Test-Path "$Root/packages") -or (Test-Path "$Root/apps") -or (Test-Path "$Root/services")) { + $type = "monorepo" + } + # Frontend + Backend split + elseif ((Test-Path "$Root/frontend") -and (Test-Path "$Root/backend")) { + $type = "fullstack-split" + } + elseif ((Test-Path "$Root/client") -and (Test-Path "$Root/server")) { + $type = "fullstack-split" + } + # Web application + elseif ((Test-Path "$Root/package.json") -and + (Select-String -Path "$Root/package.json" -Pattern "react|vue|angular|svelte|next|nuxt" -Quiet)) { + $type = "web-frontend" + } + # API/Backend + elseif ((Test-Path "$Root/api") -or (Test-Path "$Root/server")) { + $type = "backend-api" + } + # CLI tool + elseif ((Test-Path "$Root/cmd") -or + ((Test-Path "$Root/package.json") -and (Select-String -Path "$Root/package.json" -Pattern "`"bin`":" -Quiet))) { + $type = "cli-tool" + } + # Library + elseif ((Test-Path "$Root/lib") -or + ((Test-Path "$Root/Cargo.toml") -and (Select-String -Path "$Root/Cargo.toml" -Pattern "\[lib\]" -Quiet))) { + $type = "library" + } + # Generic application + elseif (Test-Path "$Root/src") { + $type = "application" + } + + return $type +} + +# Run detection +Detect-ConfigFiles $repoRoot +Detect-Directories $repoRoot +$projectType = Detect-ProjectType $repoRoot + +# Deduplicate languages +$uniqueLanguages = $languages | Select-Object -Unique + +# Output +if ($Json) { + $result = @{ + REPO_ROOT = $repoRoot + PROJECT_TYPE = $projectType + TECH_FILES = $techFiles + DIRECTORIES = $directories + LANGUAGES = $uniqueLanguages + } + $result | ConvertTo-Json -Compress +} else { + Write-Host "Repository Root: $repoRoot" + Write-Host "Project Type: $projectType" + Write-Host "" + Write-Host "Languages:" + foreach ($lang in $uniqueLanguages) { + Write-Host " - $lang" + } + Write-Host "" + Write-Host "Configuration Files:" + foreach ($file in $techFiles) { + Write-Host " - $file" + } + Write-Host "" + Write-Host "Key Directories:" + foreach ($dir in $directories) { + Write-Host " - $dir" + } +} \ No newline at end of file diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 563f2469e..6d27cc040 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1319,7 +1319,7 @@ def summarize(): ### Configuration Files {chr(10).join(f'- `{file}`' for file in tech_files[:20]) if tech_files else '- (none detected)'} -{f'- ... and {len(tech_files) - 20} more' if len(tech_files) > 20 else ''} +{(chr(10) + f'- ... and {len(tech_files) - 20} more') if len(tech_files) > 20 else ''} ## Project Structure diff --git a/templates/commands/summarize.md b/templates/commands/summarize.md index bbcb2ef64..8591a59f9 100644 --- a/templates/commands/summarize.md +++ b/templates/commands/summarize.md @@ -259,7 +259,7 @@ Create a structured summary document at `/memory/project-summary.md`: --- -*This summary was generated by `specify summarize`. Update this file as the project evolves.* +*This summary was generated by `specify summarize`. Update this file as the project evolves or re-run the command to regenerate.* ``` ### 7. Output Results diff --git a/templates/project-summary-template.md b/templates/project-summary-template.md index 75b05d6aa..48d466279 100644 --- a/templates/project-summary-template.md +++ b/templates/project-summary-template.md @@ -47,7 +47,7 @@ - Constants: [CONSTANT_NAMING] ### Code Style -- Indentation: [TABS_OR_SPACES] ([WIDTH] spaces) +- Indentation: [TABS_OR_SPACES] ([WIDTH]) - Line length: [MAX_LENGTH] characters - Style guide: [STYLE_GUIDE_IF_CONFIGURED] From b305d42165eed0051b58f9d3a7bc763ee3bebf88 Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 12:55:36 +0800 Subject: [PATCH 03/11] Update templates/commands/summarize.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- templates/commands/summarize.md | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/commands/summarize.md b/templates/commands/summarize.md index 8591a59f9..fc4ceb5ba 100644 --- a/templates/commands/summarize.md +++ b/templates/commands/summarize.md @@ -2,7 +2,6 @@ description: Generate a comprehensive summary of an existing project's technology stack, architecture, and code conventions scripts: sh: scripts/bash/generate-project-summary.sh --json - ps: scripts/powershell/generate-project-summary.ps1 -Json --- ## User Input From d88af1ffaae0d48de59f78bc9ec2c039f6810d6a Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 12:55:49 +0800 Subject: [PATCH 04/11] Update src/specify_cli/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/specify_cli/__init__.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 6d27cc040..fd29b9130 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1204,35 +1204,47 @@ def summarize(): # Find script path (could be in installed location or development location) script_name = f"generate-project-summary.{script_ext}" - # Try to find script in various locations - possible_script_paths = [ - repo_root / ".specify" / "scripts" / script_name, - repo_root / "scripts" / script_dir / script_name, - Path(__file__).parent.parent.parent / "scripts" / script_dir / script_name, - ] + # Try to find script in various locations, for both .ps1 and .sh + possible_script_names = [script_name, script_name.replace('.ps1', '.sh')] + possible_script_paths = [] + for name in possible_script_names: + possible_script_paths.extend([ + repo_root / ".specify" / "scripts" / name, + repo_root / "scripts" / script_dir / name, + Path(__file__).parent.parent.parent / "scripts" / script_dir / name, + ]) script_path = None + script_type = None for path in possible_script_paths: if path.exists(): script_path = path + if str(path).endswith('.ps1'): + script_type = 'ps1' + elif str(path).endswith('.sh'): + script_type = 'sh' break if not script_path: - tracker.error("find-script", f"Could not find {script_name}") + tracker.error("find-script", f"Could not find {script_name} (.ps1 or .sh)") console.print(tracker.render()) console.print(f"\n[red]Error: Analysis script not found. Tried:[/red]") for path in possible_script_paths: console.print(f" - {path}") raise typer.Exit(1) - tracker.add("run-script", f"Run analysis script ({script_name})") + tracker.add("run-script", f"Run analysis script ({script_path.name})") # Run the script with --json flag try: - if is_windows: + if script_type == 'ps1': cmd = ["pwsh", "-File", str(script_path), "-Json"] - else: + elif script_type == 'sh': cmd = ["/bin/bash", str(script_path), "--json"] + else: + tracker.error("run-script", f"Unknown script type for {script_path}") + console.print(tracker.render()) + raise typer.Exit(1) result = subprocess.run( cmd, From c6f5ba3a373a611eeb1d7911d82ae2d02d2c567d Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 13:01:12 +0800 Subject: [PATCH 05/11] Update scripts/bash/generate-project-summary.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/generate-project-summary.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bash/generate-project-summary.sh b/scripts/bash/generate-project-summary.sh index fad209247..8e10a5e96 100755 --- a/scripts/bash/generate-project-summary.sh +++ b/scripts/bash/generate-project-summary.sh @@ -68,7 +68,7 @@ detect_config_files() { [[ -f "$root/composer.lock" ]] && TECH_FILES+=("composer.lock") # C#/.NET - find "$root" -maxdepth 2 -name "*.csproj" -o -name "*.fsproj" -o -name "*.vbproj" | while read -r proj; do + find "$root" -maxdepth 2 \( -name "*.csproj" -o -name "*.fsproj" -o -name "*.vbproj" \) | while read -r proj; do TECH_FILES+=("$(basename "$proj")") LANGUAGES+=("C#/.NET") done From abbda7cedf243598890c6999a07579336cce23fa Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 13:01:27 +0800 Subject: [PATCH 06/11] Update templates/commands/summarize.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- templates/commands/summarize.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/commands/summarize.md b/templates/commands/summarize.md index fc4ceb5ba..ff9d99efa 100644 --- a/templates/commands/summarize.md +++ b/templates/commands/summarize.md @@ -176,7 +176,7 @@ Scan for common patterns: ### 6. Generate Project Summary -Create a structured summary document at `/memory/project-summary.md`: +Create a structured summary document at `.specify/memory/project-summary.md`: ```markdown # Project Summary From f5bbc9cc1ad61de6b342952f7ce91c50917ae17f Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 13:05:17 +0800 Subject: [PATCH 07/11] Update src/specify_cli/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/specify_cli/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index fd29b9130..84a880008 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1205,7 +1205,10 @@ def summarize(): script_name = f"generate-project-summary.{script_ext}" # Try to find script in various locations, for both .ps1 and .sh - possible_script_names = [script_name, script_name.replace('.ps1', '.sh')] + if is_windows: + possible_script_names = ["generate-project-summary.ps1", "generate-project-summary.sh"] + else: + possible_script_names = ["generate-project-summary.sh"] possible_script_paths = [] for name in possible_script_names: possible_script_paths.extend([ From 38d39091e5a26844908b22732e59a23b1ab81ec3 Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 13:05:25 +0800 Subject: [PATCH 08/11] Update scripts/powershell/generate-project-summary.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/generate-project-summary.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/powershell/generate-project-summary.ps1 b/scripts/powershell/generate-project-summary.ps1 index 160bddd35..7e80d9e24 100644 --- a/scripts/powershell/generate-project-summary.ps1 +++ b/scripts/powershell/generate-project-summary.ps1 @@ -129,9 +129,9 @@ function Detect-ConfigFiles { if (Test-Path "$Root/composer.lock") { $script:techFiles += "composer.lock" } # C#/.NET - $csprojFiles = Get-ChildItem -Path $Root -Filter "*.csproj" -File -ErrorAction SilentlyContinue - $fsprojFiles = Get-ChildItem -Path $Root -Filter "*.fsproj" -File -ErrorAction SilentlyContinue - $vbprojFiles = Get-ChildItem -Path $Root -Filter "*.vbproj" -File -ErrorAction SilentlyContinue + $csprojFiles = Get-ChildItem -Path $Root -Filter "*.csproj" -File -Depth 2 -ErrorAction SilentlyContinue + $fsprojFiles = Get-ChildItem -Path $Root -Filter "*.fsproj" -File -Depth 2 -ErrorAction SilentlyContinue + $vbprojFiles = Get-ChildItem -Path $Root -Filter "*.vbproj" -File -Depth 2 -ErrorAction SilentlyContinue if ($csprojFiles -or $fsprojFiles -or $vbprojFiles) { foreach ($proj in ($csprojFiles + $fsprojFiles + $vbprojFiles)) { From bafc32ed9681cc718760cc6160bfdba2d286b626 Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 13:33:16 +0800 Subject: [PATCH 09/11] Update src/specify_cli/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/specify_cli/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 84a880008..e0882c77f 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1333,8 +1333,12 @@ def summarize(): {chr(10).join(f'- {lang}' for lang in languages) if languages else '- (none detected)'} ### Configuration Files -{chr(10).join(f'- `{file}`' for file in tech_files[:20]) if tech_files else '- (none detected)'} -{(chr(10) + f'- ... and {len(tech_files) - 20} more') if len(tech_files) > 20 else ''} +{( + chr(10).join( + [f'- `{file}`' for file in tech_files[:20]] + + ([f'- ... and {len(tech_files) - 20} more'] if len(tech_files) > 20 else []) + ) if tech_files else '- (none detected)' +)} ## Project Structure From b5eed38cfb3a6567ac388b013dc68833eff1e398 Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 13:33:28 +0800 Subject: [PATCH 10/11] Update templates/project-summary-template.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- templates/project-summary-template.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/templates/project-summary-template.md b/templates/project-summary-template.md index 48d466279..5804d95da 100644 --- a/templates/project-summary-template.md +++ b/templates/project-summary-template.md @@ -12,9 +12,8 @@ - [FRAMEWORK_NAME]: [VERSION] ### Key Dependencies -- [DEPENDENCY_1]: [VERSION] - [PURPOSE] -- [DEPENDENCY_2]: [VERSION] - [PURPOSE] -- [DEPENDENCY_3]: [VERSION] - [PURPOSE] +- [DEPENDENCY]: [VERSION] - [PURPOSE] +... ### Package Manager - [PACKAGE_MANAGER] From 95306a8bdaa7b6a4be71722f7a97a94d99800472 Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sun, 2 Nov 2025 13:33:50 +0800 Subject: [PATCH 11/11] Update templates/project-summary-template.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- templates/project-summary-template.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/templates/project-summary-template.md b/templates/project-summary-template.md index 5804d95da..f793f44a3 100644 --- a/templates/project-summary-template.md +++ b/templates/project-summary-template.md @@ -32,9 +32,7 @@ ``` ### Key Directories -- `[DIRECTORY_1]`: [PURPOSE] -- `[DIRECTORY_2]`: [PURPOSE] -- `[DIRECTORY_3]`: [PURPOSE] +- [DIRECTORY]/ ... ## Code Conventions