diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index a50565e..0c4ad3e 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -23,7 +23,7 @@ {"id":"skills-6e3","title":"Searchable Claude Code conversation history","description":"## Context\nClaude Code persists full conversations in `~/.claude/projects/\u003cproject\u003e/\u003cuuid\u003e.jsonl`. This is complete but not searchable - can't easily find \"that session where we solved X\".\n\n## Goal\nMake conversation history searchable without requiring manual worklogs.\n\n## Approach\n\n### Index structure\n```\n~/.claude/projects/\u003cproject\u003e/\n \u003cuuid\u003e.jsonl # raw conversation (existing)\n index.jsonl # session metadata + summaries (new)\n```\n\n### Index entry format\n```json\n{\n \"uuid\": \"f9a4c161-...\",\n \"date\": \"2025-12-17\",\n \"project\": \"/home/dan/proj/skills\",\n \"summary\": \"Explored Wayland desktop automation, AT-SPI investigation, vision model benchmark\",\n \"keywords\": [\"wayland\", \"niri\", \"at-spi\", \"automation\", \"seeing-problem\"],\n \"commits\": [\"906f2bc\", \"0b97155\"],\n \"duration_minutes\": 90,\n \"message_count\": 409\n}\n```\n\n### Features needed\n1. **Index builder** - Parse JSONL, extract/generate summary + keywords\n2. **Search CLI** - `claude-search \"AT-SPI wayland\"` → matching sessions\n3. **Auto-index hook** - Update index on session end or compaction\n\n## Questions\n- Generate summaries via AI or extract heuristically?\n- Index per-project or global?\n- How to handle very long sessions (multiple topics)?\n\n## Value\n- Find past solutions without remembering dates\n- Model reflection: include relevant past sessions in context\n- Replace manual worklogs with auto-generated metadata","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-17T15:56:50.913766392-08:00","updated_at":"2025-12-29T18:35:56.530154004-05:00","closed_at":"2025-12-29T18:35:56.530154004-05:00","close_reason":"Prototype complete: bin/claude-search indexes 122 sessions, searches by keyword. Future: auto-index hook, full-text search, keyword extraction."} {"id":"skills-6gw","title":"Add artifact provenance to traces","description":"Current: files_created lists paths only.\nProblem: Can't detect regressions or validate outputs.\n\nAdd:\n- Content hash (sha256)\n- File size\n- For modifications: git_diff_summary (files changed, line counts)\n\nExample:\n outputs:\n artifacts:\n - path: docs/worklogs/...\n sha256: abc123...\n size: 1234\n action: created|modified\n\nEnables: diff traces, regression testing, validation.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-23T19:49:48.654952533-05:00","updated_at":"2025-12-29T13:55:35.827778174-05:00","closed_at":"2025-12-29T13:55:35.827778174-05:00","close_reason":"Parked with ADR-001: skills-molecules integration deferred. Current simpler approach (skills as standalone) works well. Revisit when complex orchestration needed."} {"id":"skills-6jw","title":"spec-review: Add severity labeling to prompts and reviews","description":"Reviews produce flat lists mixing blockers with minor nits. Hard to make decisions.\n\nAdd to prompts:\n- Require severity labels: Blocker / High / Medium / Low\n- Sort output by severity\n- Include impact and likelihood for each issue","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-15T00:23:23.334156366-08:00","updated_at":"2025-12-15T13:00:32.678573181-08:00","closed_at":"2025-12-15T13:00:32.678573181-08:00"} -{"id":"skills-7bu","title":"Add atomic file operations to update scripts","description":"Files affected:\n- skills/update-opencode/scripts/update-nix-file.sh\n- .specify/scripts/bash/update-agent-context.sh\n\nIssues:\n- Uses sed -i which can corrupt on error\n- No rollback mechanism despite creating backups\n- Unsafe regex patterns with complex escaping\n\nFix:\n- Write to temp file, then atomic mv\n- Validate output before replacing original\n- Add rollback on failure\n\nSeverity: MEDIUM","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-24T02:51:02.334416215-05:00","updated_at":"2025-12-24T02:51:02.334416215-05:00"} +{"id":"skills-7bu","title":"Add atomic file operations to update scripts","description":"Files affected:\n- skills/update-opencode/scripts/update-nix-file.sh\n- .specify/scripts/bash/update-agent-context.sh\n\nIssues:\n- Uses sed -i which can corrupt on error\n- No rollback mechanism despite creating backups\n- Unsafe regex patterns with complex escaping\n\nFix:\n- Write to temp file, then atomic mv\n- Validate output before replacing original\n- Add rollback on failure\n\nSeverity: MEDIUM","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-24T02:51:02.334416215-05:00","updated_at":"2026-01-03T12:08:56.822659199-08:00","closed_at":"2026-01-03T12:08:56.822659199-08:00","close_reason":"Implemented atomic updates using temp files and traps in update-nix-file.sh, update-agent-context.sh, and deploy-skill.sh. Added validation before replacing original files."} {"id":"skills-7s0","title":"Compare STATIC_DATA.md with upstream","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T20:15:55.193704589-08:00","updated_at":"2025-12-03T20:19:29.659256809-08:00","closed_at":"2025-12-03T20:19:29.659256809-08:00","dependencies":[{"issue_id":"skills-7s0","depends_on_id":"skills-ebh","type":"discovered-from","created_at":"2025-12-03T20:15:55.195160705-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"skills-7sh","title":"Set up bd-issue-tracking Claude Code skill from beads repo","description":"Install the beads Claude Code skill from https://github.com/steveyegge/beads/tree/main/examples/claude-code-skill\n\nThis skill teaches Claude how to effectively use beads for issue tracking across multi-session coding workflows. It provides strategic guidance on when/how to use beads, not just command syntax.\n\nFiles to install to ~/.claude/skills/bd-issue-tracking/:\n- SKILL.md - Core workflow patterns and decision criteria\n- BOUNDARIES.md - When to use beads vs markdown alternatives\n- CLI_REFERENCE.md - Complete command documentation\n- DEPENDENCIES.md - Relationship types and patterns\n- WORKFLOWS.md - Step-by-step procedures\n- ISSUE_CREATION.md - Quality guidelines\n- RESUMABILITY.md - Making work resumable across sessions\n- STATIC_DATA.md - Using beads as reference databases\n\nCan symlink or copy the files. Restart Claude Code after install.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-03T17:53:43.254007992-08:00","updated_at":"2025-12-03T20:04:53.416579381-08:00","closed_at":"2025-12-03T20:04:53.416579381-08:00"} {"id":"skills-8cc","title":"Remove dead code: unused ARGS variable","description":"File: .specify/scripts/bash/create-new-feature.sh\n\nLine 8: ARGS=() declared but never used\nLine 251: export SPECIFY_FEATURE - unclear if used downstream\n\nFix:\n- Remove unused ARGS declaration\n- Verify SPECIFY_FEATURE is used or remove\n\nSeverity: LOW","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-24T02:50:59.332192076-05:00","updated_at":"2025-12-29T18:38:03.48883384-05:00","closed_at":"2025-12-29T18:38:03.48883384-05:00","close_reason":"Invalid: ARGS is used (line 58, 64). SPECIFY_FEATURE is used by common.sh for feature detection. No dead code."} diff --git a/.specify/scripts/bash/create-new-feature.sh b/.specify/scripts/bash/create-new-feature.sh index 86d9ecf..fd4202b 100755 --- a/.specify/scripts/bash/create-new-feature.sh +++ b/.specify/scripts/bash/create-new-feature.sh @@ -2,71 +2,6 @@ set -e -JSON_MODE=false -SHORT_NAME="" -BRANCH_NUMBER="" -ARGS=() -i=1 -while [ $i -le $# ]; do - arg="${!i}" - case "$arg" in - --json) - JSON_MODE=true - ;; - --short-name) - if [ $((i + 1)) -gt $# ]; then - echo 'Error: --short-name requires a value' >&2 - exit 1 - fi - i=$((i + 1)) - next_arg="${!i}" - # Check if the next argument is another option (starts with --) - if [[ "$next_arg" == --* ]]; then - echo 'Error: --short-name requires a value' >&2 - exit 1 - fi - SHORT_NAME="$next_arg" - ;; - --number) - if [ $((i + 1)) -gt $# ]; then - echo 'Error: --number requires a value' >&2 - exit 1 - fi - i=$((i + 1)) - next_arg="${!i}" - if [[ "$next_arg" == --* ]]; then - echo 'Error: --number requires a value' >&2 - exit 1 - fi - BRANCH_NUMBER="$next_arg" - ;; - --help|-h) - echo "Usage: $0 [--json] [--short-name ] [--number N] " - echo "" - echo "Options:" - echo " --json Output in JSON format" - echo " --short-name Provide a custom short name (2-4 words) for the branch" - echo " --number N Specify branch number manually (overrides auto-detection)" - echo " --help, -h Show this help message" - echo "" - echo "Examples:" - echo " $0 'Add user authentication system' --short-name 'user-auth'" - echo " $0 'Implement OAuth2 integration for API' --number 5" - exit 0 - ;; - *) - ARGS+=("$arg") - ;; - esac - i=$((i + 1)) -done - -FEATURE_DESCRIPTION="${ARGS[*]}" -if [ -z "$FEATURE_DESCRIPTION" ]; then - echo "Usage: $0 [--json] [--short-name ] [--number N] " >&2 - exit 1 -fi - # Function to find the repository root by searching for existing project markers find_repo_root() { local dir="$1" @@ -83,20 +18,21 @@ find_repo_root() { # Function to check existing branches (local and remote) and return next available number check_existing_branches() { local short_name="$1" + local specs_dir="$2" # Fetch all remotes to get latest branch info (suppress errors if no remotes) git fetch --all --prune 2>/dev/null || true # Find all branches matching the pattern using git ls-remote (more reliable) - local remote_branches=$(git ls-remote --heads origin 2>/dev/null | grep -E "refs/heads/[0-9]+-${short_name}$" | sed 's/.*\/\([0-9]*\)-.*/\1/' | sort -n) + local remote_branches=$(git ls-remote --heads origin 2>/dev/null | grep -E "refs/heads/[0-9]+-${short_name}$" | sed 's/.*\/\([0-9]*\)-.* /\1/' | sort -n) # Also check local branches local local_branches=$(git branch 2>/dev/null | grep -E "^[* ]*[0-9]+-${short_name}$" | sed 's/^[* ]*//' | sed 's/-.*//' | sort -n) # Check specs directory as well local spec_dirs="" - if [ -d "$SPECS_DIR" ]; then - spec_dirs=$(find "$SPECS_DIR" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n) + if [ -d "$specs_dir" ]; then + spec_dirs=$(find "$specs_dir" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n) fi # Combine all sources and get the highest number @@ -111,28 +47,6 @@ check_existing_branches() { echo $((max_num + 1)) } -# Resolve repository root. Prefer git information when available, but fall back -# to searching for repository markers so the workflow still functions in repositories that -# were initialised with --no-git. -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -if git rev-parse --show-toplevel >/dev/null 2>&1; then - REPO_ROOT=$(git rev-parse --show-toplevel) - HAS_GIT=true -else - REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")" - if [ -z "$REPO_ROOT" ]; then - echo "Error: Could not determine repository root. Please run this script from within the repository." >&2 - exit 1 - fi - HAS_GIT=false -fi - -cd "$REPO_ROOT" - -SPECS_DIR="$REPO_ROOT/specs" -mkdir -p "$SPECS_DIR" - # Function to generate branch name with stop word filtering and length filtering generate_branch_name() { local description="$1" @@ -140,121 +54,175 @@ generate_branch_name() { # Common stop words to filter out local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" - # Convert to lowercase and split into words - local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') + # Split into words and lowercase + local words=$(echo "$description" | tr '[:upper:]' '[:lower:]' | tr -s '[:punct:][:space:]' '\n') - # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) local meaningful_words=() - for word in $clean_name; do - # Skip empty words - [ -z "$word" ] && continue + for word in $words; do + [[ -z "$word" ]] && continue - # Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms) + # Keep if not a stop word AND (length >= 3 OR uppercase acronym in original) if ! echo "$word" | grep -qiE "$stop_words"; then - if [ ${#word} -ge 3 ]; then - meaningful_words+=("$word") - elif echo "$description" | grep -q "\b${word^^}\b"; then - # Keep short words if they appear as uppercase in original (likely acronyms) + if [[ ${#word} -ge 3 ]] || echo "$description" | grep -q "\b${word^^}\b"; then meaningful_words+=("$word") fi fi done - # If we have meaningful words, use first 3-4 of them - if [ ${#meaningful_words[@]} -gt 0 ]; then - local max_words=3 - if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi - - local result="" - local count=0 - for word in "${meaningful_words[@]}"; do - if [ $count -ge $max_words ]; then break; fi - if [ -n "$result" ]; then result="$result-"; fi - result="$result$word" - count=$((count + 1)) - done - echo "$result" + if [[ ${#meaningful_words[@]} -gt 0 ]]; then + # Use first 4 meaningful words joined by hyphens + printf "%s\n" "${meaningful_words[@]}" | head -n 4 | tr '\n' '-' | sed 's/-$//' else - # Fallback to original logic if no meaningful words found - echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' + # Fallback: just take first 3 non-empty words + echo "$description" | tr '[:upper:]' '[:lower:]' | tr -s '[:punct:][:space:]' '\n' | grep -v '^$' | head -n 3 | tr '\n' '-' | sed 's/-$//' fi } -# Generate branch name -if [ -n "$SHORT_NAME" ]; then - # Use provided short name, just clean it up - BRANCH_SUFFIX=$(echo "$SHORT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//') -else - # Generate from description with smart filtering - BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") -fi +main() { + local json_mode=false + local short_name="" + local branch_number="" + local args=() + local i=1 + + while [ $i -le $# ]; do + local arg="${!i}" + case "$arg" in + --json) + json_mode=true + ;; + --short-name) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + local next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + short_name="$next_arg" + ;; + --number) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + local next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + branch_number="$next_arg" + ;; + --help|-h) + echo "Usage: $0 [--json] [--short-name ] [--number N] " + echo "" + echo "Options:" + echo " --json Output in JSON format" + echo " --short-name Provide a custom short name (2-4 words) for the branch" + echo " --number N Specify branch number manually (overrides auto-detection)" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " $0 'Add user authentication system' --short-name 'user-auth'" + echo " $0 'Implement OAuth2 integration for API' --number 5" + return 0 + ;; + *) + args+=("$arg") + ;; + esac + i=$((i + 1)) + done -# Determine branch number -if [ -z "$BRANCH_NUMBER" ]; then - if [ "$HAS_GIT" = true ]; then - # Check existing branches on remotes - BRANCH_NUMBER=$(check_existing_branches "$BRANCH_SUFFIX") - else - # Fall back to local directory check - HIGHEST=0 - if [ -d "$SPECS_DIR" ]; then - for dir in "$SPECS_DIR"/*; do - [ -d "$dir" ] || continue - dirname=$(basename "$dir") - number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0") - number=$((10#$number)) - if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi - done - fi - BRANCH_NUMBER=$((HIGHEST + 1)) + local feature_description="${args[*]}" + if [ -z "$feature_description" ]; then + echo "Usage: $0 [--json] [--short-name ] [--number N] " >&2 + exit 1 fi -fi -FEATURE_NUM=$(printf "%03d" "$BRANCH_NUMBER") -BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" + local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + local repo_root + local has_git -# GitHub enforces a 244-byte limit on branch names -# Validate and truncate if necessary -MAX_BRANCH_LENGTH=244 -if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then - # Calculate how much we need to trim from suffix - # Account for: feature number (3) + hyphen (1) = 4 chars - MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4)) - - # Truncate suffix at word boundary if possible - TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) - # Remove trailing hyphen if truncation created one - TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') - - ORIGINAL_BRANCH_NAME="$BRANCH_NAME" - BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" - - >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" - >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" - >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" -fi + if git rev-parse --show-toplevel >/dev/null 2>&1; then + repo_root=$(git rev-parse --show-toplevel) + has_git=true + else + repo_root="$(find_repo_root "$script_dir")" + if [ -z "$repo_root" ]; then + echo "Error: Could not determine repository root." >&2 + exit 1 + fi + has_git=false + fi -if [ "$HAS_GIT" = true ]; then - git checkout -b "$BRANCH_NAME" -else - >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" -fi + cd "$repo_root" + local specs_dir="$repo_root/specs" + mkdir -p "$specs_dir" -FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" -mkdir -p "$FEATURE_DIR" + # Generate branch name suffix + local branch_suffix + if [ -n "$short_name" ]; then + branch_suffix=$(echo "$short_name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//') + else + branch_suffix=$(generate_branch_name "$feature_description") + fi -TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md" -SPEC_FILE="$FEATURE_DIR/spec.md" -if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi + # Determine feature number + local feature_num + if [ -n "$branch_number" ]; then + feature_num="$branch_number" + else + feature_num=$(check_existing_branches "$branch_suffix" "$specs_dir") + fi -# Set the SPECIFY_FEATURE environment variable for the current session -export SPECIFY_FEATURE="$BRANCH_NAME" + local branch_name="${feature_num}-${branch_suffix}" -if $JSON_MODE; then - printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" -else - echo "BRANCH_NAME: $BRANCH_NAME" - echo "SPEC_FILE: $SPEC_FILE" - echo "FEATURE_NUM: $FEATURE_NUM" - echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME" -fi + # Validate and truncate if necessary + local max_branch_length=244 + if [ ${#branch_name} -gt $max_branch_length ]; then + local max_suffix_length=$((max_branch_length - 4)) + local truncated_suffix=$(echo "$branch_suffix" | cut -c1-$max_suffix_length | sed 's/-$//') + + local original_branch_name="$branch_name" + branch_name="${feature_num}-${truncated_suffix}" + + >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" + >&2 echo "[specify] Original: $original_branch_name (${#original_branch_name} bytes)" + >&2 echo "[specify] Truncated to: $branch_name (${#branch_name} bytes)" + fi + + if [ "$has_git" = true ]; then + git checkout -b "$branch_name" + else + >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $branch_name" + fi + + local feature_dir="$specs_dir/$branch_name" + mkdir -p "$feature_dir" + + local template="$repo_root/.specify/templates/spec-template.md" + local spec_file="$feature_dir/spec.md" + if [ -f "$template" ]; then cp "$template" "$spec_file"; else touch "$spec_file"; fi + + # Set the SPECIFY_FEATURE environment variable for the current session + export SPECIFY_FEATURE="$branch_name" + + if $json_mode; then + printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$branch_name" "$spec_file" "$feature_num" + else + echo "BRANCH_NAME: $branch_name" + echo "SPEC_FILE: $spec_file" + echo "FEATURE_NUM: $feature_num" + echo "SPECIFY_FEATURE environment variable set to: $branch_name" + fi +} + +# Execute main function if script is run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file