Fix: Resolve bash command truncation and syntax errors (#958)

* fix: resolve bash command truncation and syntax errors

- Convert long one-liner bash commands to multiline format to prevent truncation
- Fix awk syntax errors in epic-sync.md (replace literal \n with proper newlines)
- Add missing directory separators in file path patterns
- Replace chained && operators with proper if-then blocks
- Break down complex pipe chains into step-by-step operations

Key changes:
* ccpm/scripts/pm/*.sh: Convert complex dependency parsing from one-liners to readable multiline blocks
* ccpm/ccpm.config: Break down repository URL processing into clear steps
* ccpm/commands/pm/epic-sync.md: Fix awk print statements for proper newline handling

Fixes command truncation errors like:
- Error: (eval):1: parse error near ')'
- awk: syntax error at source line 1
- command not found: !

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: additional bash syntax fixes in command documentation

- epic-merge.md: Convert complex nested commands to multiline format
  - Fix feature list generation with proper error handling
  - Break down epic issue number extraction
  - Add proper file existence checks
- epic-refresh.md: Fix task issue extraction with error handling
- epic-sync.md: Re-fix awk syntax for proper newline handling
- test-runner.md: Remove MUXI-specific reference

Prevents additional command truncation in:
- Complex subshell operations with cd and loops
- Chained grep operations for GitHub issue extraction
- Nested command substitution patterns

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address CodeRabbit feedback on dependency parsing and SSH URL handling

- ccpm.config: Add support for ssh://git@github.com/ and ssh://github.com/ URL schemes
  - Ensures all GitHub URL variants are properly normalized
- All PM scripts: Fix dependency parsing to handle whitespace-only cases
  - Trim leading/trailing whitespace after bracket removal
  - Properly handle empty dependency lists like "depends_on: [ ]"
  - Normalize whitespace-only strings to empty strings
  - Prevents false positive dependency detection

Fixes issues where:
- SSH URLs starting with ssh://git@github.com/ would fail parsing
- Empty dependency arrays with whitespace were treated as non-empty
- Tasks with "depends_on: [ ]" were incorrectly blocked

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Ran Aroussi
2025-09-24 22:32:39 +01:00
committed by GitHub
parent 1cb9483a74
commit 3c8e0e7f67
10 changed files with 128 additions and 26 deletions

View File

@@ -6,7 +6,7 @@ model: inherit
color: blue
---
You are an expert test execution and analysis specialist for the MUXI Runtime system. Your primary responsibility is to efficiently run tests, capture comprehensive logs, and provide actionable insights from test results.
You are an expert test execution and analysis specialist. Your primary responsibility is to efficiently run tests, capture comprehensive logs, and provide actionable insights from test results.
## Core Responsibilities

View File

@@ -11,8 +11,15 @@ get_github_repo() {
return 1
fi
# Handle both SSH and HTTPS, with or without .git extension
local repo=$(echo "$remote_url" | sed -E 's#^(https://|git@)github\.com[:/]##; s#\.git$##')
# Handle HTTPS, SSH, and SCP-style URLs
local repo="$remote_url"
# Remove various GitHub URL prefixes
repo=$(echo "$repo" | sed -E 's#^https://github\.com/##')
repo=$(echo "$repo" | sed -E 's#^git@github\.com:##')
repo=$(echo "$repo" | sed -E 's#^ssh://git@github\.com/##')
repo=$(echo "$repo" | sed -E 's#^ssh://github\.com/##')
# Remove .git suffix if present
repo=$(echo "$repo" | sed 's#\.git$##')
# Validate format
if [[ ! "$repo" =~ ^[^/]+/[^/]+$ ]]; then

View File

@@ -98,11 +98,28 @@ echo "Merging epic/$ARGUMENTS to main..."
git merge epic/$ARGUMENTS --no-ff -m "Merge epic: $ARGUMENTS
Completed features:
$(cd .claude/epics/$ARGUMENTS && ls *.md | grep -E '^[0-9]+' | while read f; do
echo "- $(grep '^name:' $f | cut -d: -f2)"
done)
# Generate feature list
feature_list=""
if [ -d ".claude/epics/$ARGUMENTS" ]; then
cd .claude/epics/$ARGUMENTS
for task_file in [0-9]*.md; do
[ -f "$task_file" ] || continue
task_name=$(grep '^name:' "$task_file" | cut -d: -f2 | sed 's/^ *//')
feature_list="$feature_list\n- $task_name"
done
cd - > /dev/null
fi
Closes epic #$(grep 'github:' .claude/epics/$ARGUMENTS/epic.md | grep -oE '#[0-9]+')"
echo "$feature_list"
# Extract epic issue number
epic_github_line=$(grep 'github:' .claude/epics/$ARGUMENTS/epic.md 2>/dev/null || true)
if [ -n "$epic_github_line" ]; then
epic_issue=$(echo "$epic_github_line" | grep -oE '[0-9]+' || true)
if [ -n "$epic_issue" ]; then
echo "\nCloses epic #$epic_issue"
fi
fi"
```
### 5. Handle Merge Conflicts
@@ -161,14 +178,27 @@ echo "✅ Epic archived: .claude/epics/archived/$ARGUMENTS"
Close related issues:
```bash
# Get issue numbers from epic
epic_issue=$(grep 'github:' .claude/epics/archived/$ARGUMENTS/epic.md | grep -oE '[0-9]+$')
# Extract epic issue number
epic_github_line=$(grep 'github:' .claude/epics/archived/$ARGUMENTS/epic.md 2>/dev/null || true)
if [ -n "$epic_github_line" ]; then
epic_issue=$(echo "$epic_github_line" | grep -oE '[0-9]+$' || true)
else
epic_issue=""
fi
# Close epic issue
gh issue close $epic_issue -c "Epic completed and merged to main"
# Close task issues
for task_file in .claude/epics/archived/$ARGUMENTS/[0-9]*.md; do
issue_num=$(grep 'github:' $task_file | grep -oE '[0-9]+$')
[ -f "$task_file" ] || continue
# Extract task issue number
task_github_line=$(grep 'github:' "$task_file" 2>/dev/null || true)
if [ -n "$task_github_line" ]; then
issue_num=$(echo "$task_github_line" | grep -oE '[0-9]+$' || true)
else
issue_num=""
fi
if [ ! -z "$issue_num" ]; then
gh issue close $issue_num -c "Completed in epic merge"
fi

View File

@@ -43,7 +43,13 @@ if [ ! -z "$epic_issue" ]; then
# For each task, check its status and update checkbox
for task_file in .claude/epics/$ARGUMENTS/[0-9]*.md; do
task_issue=$(grep 'github:' $task_file | grep -oE '[0-9]+$')
# Extract task issue number
task_github_line=$(grep 'github:' "$task_file" 2>/dev/null || true)
if [ -n "$task_github_line" ]; then
task_issue=$(echo "$task_github_line" | grep -oE '[0-9]+$' || true)
else
task_issue=""
fi
task_status=$(grep 'status:' $task_file | cut -d: -f2 | tr -d ' ')
if [ "$task_status" = "closed" ]; then

View File

@@ -79,7 +79,8 @@ awk '
in_tasks=0
# When we hit the next section after Tasks Created, add Stats
if (total_tasks) {
print "## Stats\n"
print "## Stats"
print ""
print "Total tasks: " total_tasks
print "Parallel tasks: " parallel_tasks " (can be worked on simultaneously)"
print "Sequential tasks: " sequential_tasks " (have dependencies)"
@@ -99,7 +100,8 @@ awk '
END {
# If we were still in tasks section at EOF, add stats
if (in_tasks && total_tasks) {
print "## Stats\n"
print "## Stats"
print ""
print "Total tasks: " total_tasks
print "Parallel tasks: " parallel_tasks " (can be worked on simultaneously)"
print "Sequential tasks: " sequential_tasks " (have dependencies)"

View File

@@ -13,15 +13,28 @@ for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
epic_name=$(basename "$epic_dir")
for task_file in "$epic_dir"[0-9]*.md; do
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
# Check if task is open
status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
[ "$status" != "open" ] && [ -n "$status" ] && continue
if [ "$status" != "open" ] && [ -n "$status" ]; then
continue
fi
# Check for dependencies
deps=$(grep "^depends_on:" "$task_file" | head -1 | sed 's/^depends_on: *\[//' | sed 's/\]//' | sed 's/,/ /g')
# Extract dependencies from task file
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//')
deps=$(echo "$deps" | sed 's/^\[//' | sed 's/\]$//')
deps=$(echo "$deps" | sed 's/,/ /g')
# Trim whitespace and handle empty cases
deps=$(echo "$deps" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
if [ -n "$deps" ] && [ "$deps" != "depends_on:" ]; then
task_name=$(grep "^name:" "$task_file" | head -1 | sed 's/^name: *//')

View File

@@ -3,8 +3,15 @@ echo "Getting epics..."
echo ""
echo ""
[ ! -d ".claude/epics" ] && echo "📁 No epics directory found. Create your first epic with: /pm:prd-parse <feature-name>" && exit 0
[ -z "$(ls -d .claude/epics/*/ 2>/dev/null)" ] && echo "📁 No epics found. Create your first epic with: /pm:prd-parse <feature-name>" && exit 0
if [ ! -d ".claude/epics" ]; then
echo "📁 No epics directory found. Create your first epic with: /pm:prd-parse <feature-name>"
exit 0
fi
epic_dirs=$(ls -d .claude/epics/*/ 2>/dev/null || true)
if [ -z "$epic_dirs" ]; then
echo "📁 No epics found. Create your first epic with: /pm:prd-parse <feature-name>"
exit 0
fi
echo "📚 Project Epics"
echo "================"
@@ -31,7 +38,7 @@ for dir in .claude/epics/*/; do
[ -z "$p" ] && p="0%"
# Count tasks
t=$(ls "$dir"[0-9]*.md 2>/dev/null | wc -l)
t=$(ls "$dir"/[0-9]*.md 2>/dev/null | wc -l)
# Format output with GitHub issue number if available
if [ -n "$g" ]; then

View File

@@ -14,15 +14,27 @@ for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
epic_name=$(basename "$epic_dir")
for task_file in "$epic_dir"[0-9]*.md; do
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
# Check if task is open
status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
[ "$status" != "open" ] && [ -n "$status" ] && continue
if [ "$status" != "open" ] && [ -n "$status" ]; then
continue
fi
# Check dependencies
deps=$(grep "^depends_on:" "$task_file" | head -1 | sed 's/^depends_on: *\[//' | sed 's/\]//')
# Extract dependencies from task file
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//')
deps=$(echo "$deps" | sed 's/^\[//' | sed 's/\]$//')
# Trim whitespace and handle empty cases
deps=$(echo "$deps" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
# If no dependencies or empty, task is available
if [ -z "$deps" ] || [ "$deps" = "depends_on:" ]; then

View File

@@ -51,12 +51,24 @@ echo "⏭️ Next Available Tasks:"
count=0
for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
for task_file in "$epic_dir"[0-9]*.md; do
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
[ "$status" != "open" ] && [ -n "$status" ] && continue
if [ "$status" != "open" ] && [ -n "$status" ]; then
continue
fi
deps=$(grep "^depends_on:" "$task_file" | head -1 | sed 's/^depends_on: *\[//' | sed 's/\]//')
# Extract dependencies from task file
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//')
deps=$(echo "$deps" | sed 's/^\[//' | sed 's/\]$//')
# Trim whitespace and handle empty cases
deps=$(echo "$deps" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
if [ -z "$deps" ] || [ "$deps" = "depends_on:" ]; then
task_name=$(grep "^name:" "$task_file" | head -1 | sed 's/^name: *//')
task_num=$(basename "$task_file" .md)

View File

@@ -42,7 +42,18 @@ echo "🔗 Reference Check:"
for task_file in .claude/epics/*/[0-9]*.md; do
[ -f "$task_file" ] || continue
deps=$(grep "^depends_on:" "$task_file" | head -1 | sed 's/^depends_on: *\[//' | sed 's/\]//' | sed 's/,/ /g')
# Extract dependencies from task file
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//')
deps=$(echo "$deps" | sed 's/^\[//' | sed 's/\]$//')
deps=$(echo "$deps" | sed 's/,/ /g')
# Trim whitespace and handle empty cases
deps=$(echo "$deps" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
if [ -n "$deps" ] && [ "$deps" != "depends_on:" ]; then
epic_dir=$(dirname "$task_file")
for dep in $deps; do
@@ -54,7 +65,9 @@ for task_file in .claude/epics/*/[0-9]*.md; do
fi
done
[ $warnings -eq 0 ] && [ $errors -eq 0 ] && echo " ✅ All references valid"
if [ $warnings -eq 0 ] && [ $errors -eq 0 ]; then
echo " ✅ All references valid"
fi
# Check frontmatter
echo ""