| name | backlog-setup |
| description | One-time initializer for the backlog task board. Checks that the backlog CLI is installed, initializes a backlog project if none exists, and verifies that all columns required by loop-backlog and feature-to-backlog are present. Idempotent โ safe to run multiple times. |
| allowed-tools | Bash, Read, Write |
| contracts | [{"grep":"backlogSetup","target":"self","description":"Main entry function backlogSetup() must remain defined"},{"grep":"verifyColumns","target":"self","description":"Column verification step must not be removed"},{"grep":"seedExamples","target":"self","description":"Seed examples step must remain present"},{"grep":"initProject","target":"self","description":"Project init step must remain present"},{"grep":"config.yml","target":"self","description":"Implementation must edit config.yml directly (backlog column add removed in CLI v1.45+)"},{"grep":"initL0Config","target":"self","description":"L0 Config initialisation step must remain present"}] |
ฮป() โ backlogSetup()
Spec
-- Columns required by each skill
REQUIRED_COLUMNS := [
"Epic: Proposal", "Epic: Plan", "Epic: Backlog", "Epic: Ready",
"Epic: Decomposing", "Epic: Awaiting Children", "Epic: Evaluating",
"Epic: Done", "Epic: Needs Human",
"Basic: Proposal", "Basic: Plan", "Basic: Backlog", "Basic: Ready",
"Basic: In Progress", "Basic: Done", "Basic: Needs Human"
]
backlogSetup :: () โ SetupResult
backlogSetup() = {
_: checkCli(),
_: initProject(),
missing: verifyColumns(REQUIRED_COLUMNS),
_: addColumns(missing),
_: initL0Config(),
_: seedExamples(),
_: printSummary()
}
-- Only seeds when backlog/docs/ and backlog/decisions/ are both empty
-- (i.e. first-time init). Idempotent: skips if any file already exists.
seedExamples :: () โ ()
seedExamples() =
| ยฌempty(backlog/docs/) โจ ยฌempty(backlog/decisions/) โ skip
| otherwise โ {
backlog document create --type guide,
backlog decision create --status "Accepted"
}
checkCli :: () โ ()
checkCli() =
| which("backlog") succeeds โ continue
| otherwise โ print(installInstructions); halt
initProject :: () โ ()
initProject() =
| exists("backlog/") โ skip -- already initialised
| otherwise โ backlog init --defaults --agent-instructions none
verifyColumns :: [String] โ [String]
verifyColumns(required) = required โ existingColumns()
addColumns :: [String] โ ()
addColumns(cols) = โcol โ cols: backlog column add col
-- Detects project type from marker files and appends ## L0 Config to CLAUDE.md.
-- Idempotent: skips if ## L0 Config already present.
-- Pauses for human confirmation before writing.
initL0Config :: () โ ()
initL0Config() =
| grep -q "## L0 Config" CLAUDE.md โ skip
| otherwise โ {
type: detectProjectType(),
block: renderL0Block(type),
_: printProposed(block),
_: awaitConfirmation(),
_: appendOrCreateClaudeMd(block)
}
detectProjectType :: () โ ProjectType
detectProjectType() =
| exists("scripts/validate-plugin.sh") โ baime
| exists("package.json") โ node
| exists("go.mod") โ go
| exists("Cargo.toml") โ rust
| exists("pyproject.toml") โ python
| exists("Makefile") โ make
| otherwise โ unknown
Implementation
checkCli
if ! command -v backlog &>/dev/null; then
echo "โ 'backlog' CLI not found."
echo ""
echo "Install instructions:"
echo " npm install -g @backlog-md/cli"
echo " # or: pip install backlog-md"
echo " # or: see https://github.com/backlog-md/backlog"
echo ""
echo "After installing, re-run /backlog-setup."
exit 1
fi
echo "โ backlog CLI found: $(backlog --version 2>/dev/null || echo 'version unknown')"
initProject
if [ ! -d "backlog" ]; then
echo "Initialising backlog project..."
PROJECT_NAME=$(basename "$PWD")
backlog init "$PROJECT_NAME" --defaults --agent-instructions none
echo "โ backlog project initialised"
else
echo "โ backlog project already exists"
fi
verifyColumns + addColumns
backlog column add does not exist in backlog CLI v1.45+. Statuses must be set by
editing backlog/config.yml directly. Use Python to parse and rewrite the YAML line
in-place, preserving all other fields.
REQUIRED_COLUMNS=(
"Epic: Proposal" "Epic: Plan" "Epic: Backlog" "Epic: Ready"
"Epic: Decomposing" "Epic: Awaiting Children" "Epic: Evaluating"
"Epic: Done" "Epic: Needs Human"
"Basic: Proposal" "Basic: Plan" "Basic: Backlog" "Basic: Ready"
"Basic: In Progress" "Basic: Done" "Basic: Needs Human"
)
python3 - <<'PYEOF'
import re, sys
CONFIG = "backlog/config.yml"
REQUIRED = [
"Epic: Proposal", "Epic: Plan", "Epic: Backlog", "Epic: Ready",
"Epic: Decomposing", "Epic: Awaiting Children", "Epic: Evaluating",
"Epic: Done", "Epic: Needs Human",
"Basic: Proposal", "Basic: Plan", "Basic: Backlog", "Basic: Ready",
"Basic: In Progress", "Basic: Done", "Basic: Needs Human",
]
with open(CONFIG) as f:
content = f.read()
m = re.search(r'^statuses:\s*\[([^\]]*)\]', content, re.MULTILINE)
existing = []
if m:
existing = [s.strip().strip('"') for s in m.group(1).split(',') if s.strip()]
missing = [c for c in REQUIRED if c not in existing]
new_statuses = ', '.join(f'"{c}"' for c in REQUIRED)
content = re.sub(
r'^statuses:\s*\[.*?\]',
f'statuses: [{new_statuses}]',
content, flags=re.MULTILINE
)
content = re.sub(
r'^default_status:\s*"[^"]*"',
'default_status: "Basic: Proposal"',
content, flags=re.MULTILINE
)
with open(CONFIG, 'w') as f:
f.write(content)
if missing:
print("Added: " + ", ".join(missing))
else:
print("All required columns already present.")
PYEOF
initL0Config
if grep -q "## L0 Config" CLAUDE.md 2>/dev/null; then
echo "โ CLAUDE.md already contains ## L0 Config โ skipping"
else
if [ -f "scripts/validate-plugin.sh" ]; then
PROJECT_TYPE="baime"
TEST_CMD="bash scripts/validate-plugin.sh"
TEST_ALL="bash scripts/validate-plugin.sh"
DOC_PATH="docs"
WORKTREE_SYMLINKS=""
elif [ -f "package.json" ]; then
PROJECT_TYPE="node"
TEST_CMD="npm test"
TEST_ALL="npm test"
DOC_PATH="docs"
WORKTREE_SYMLINKS="node_modules"
elif [ -f "go.mod" ]; then
PROJECT_TYPE="go"
TEST_CMD="go test ./..."
TEST_ALL="go test ./..."
DOC_PATH="docs"
WORKTREE_SYMLINKS=""
elif [ -f "Cargo.toml" ]; then
PROJECT_TYPE="rust"
TEST_CMD="cargo test"
TEST_ALL="cargo test"
DOC_PATH="docs"
WORKTREE_SYMLINKS=""
elif [ -f "pyproject.toml" ]; then
PROJECT_TYPE="python"
TEST_CMD="pytest"
TEST_ALL="pytest"
DOC_PATH="docs"
WORKTREE_SYMLINKS=""
elif [ -f "Makefile" ]; then
PROJECT_TYPE="make"
TEST_CMD="make test"
TEST_ALL="make test"
DOC_PATH="docs"
WORKTREE_SYMLINKS=""
else
PROJECT_TYPE="unknown"
TEST_CMD="<fill in>"
TEST_ALL="<fill in>"
DOC_PATH="docs"
WORKTREE_SYMLINKS=""
fi
L0_BLOCK="## L0 Config
test-cmd: ${TEST_CMD}
test-all: ${TEST_ALL}
doc-path: ${DOC_PATH}
worktree-symlinks: ${WORKTREE_SYMLINKS}"
echo ""
echo "Detected project type: ${PROJECT_TYPE}"
echo ""
echo "Proposed ## L0 Config block to append to CLAUDE.md:"
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo "${L0_BLOCK}"
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo ""
printf "Write this block to CLAUDE.md? [y/N] "
read -r CONFIRM
if [ "${CONFIRM}" = "y" ] || [ "${CONFIRM}" = "Y" ]; then
if [ ! -f "CLAUDE.md" ]; then
printf "%s\n" "${L0_BLOCK}" > CLAUDE.md
echo "โ Created CLAUDE.md with ## L0 Config"
else
printf "\n%s\n" "${L0_BLOCK}" >> CLAUDE.md
echo "โ Appended ## L0 Config to CLAUDE.md"
fi
else
echo "Skipped โ edit CLAUDE.md manually to add ## L0 Config"
fi
fi
seedExamples
Run only when both backlog/docs/ and backlog/decisions/ are empty (first-time init).
DOCS_EMPTY=true
DECISIONS_EMPTY=true
[ -d backlog/docs ] && [ -n "$(ls -A backlog/docs 2>/dev/null)" ] && DOCS_EMPTY=false
[ -d backlog/decisions ] && [ -n "$(ls -A backlog/decisions 2>/dev/null)" ] && DECISIONS_EMPTY=false
if $DOCS_EMPTY && $DECISIONS_EMPTY; then
backlog document create "Backlog Web UI ๅฟซ้ๅ่" --type guide --tags "onboarding,howto" <<'EOF'
`backlog browser` ๆๅผ web UI๏ผ้กถ้จๅฏผ่ชๆไธไธชๅบๅ๏ผ
| ๅบๅ | CLI ๅฝไปค | ๅญๅจ่ทฏๅพ | ็จ้ |
|---|---|---|---|
| **TASKS** | `backlog task create` | `backlog/tasks/` | ๅ่ฝใ็ผบ้ทใไปปๅก |
| **DOCUMENTS** | `backlog document create` | `backlog/docs/` | ๆๅใ่ง่ใๅ่ๆๆกฃ |
| **DECISIONS** | `backlog decision create` | `backlog/decisions/` | ๆถๆๅณ็ญ่ฎฐๅฝ๏ผADR๏ผ |
- ๆๅจๅจ `backlog/docs/` ๆ `backlog/decisions/` ๅๅปบ็ `.md` ๆไปถ๏ผ**่ฅๆฒกๆ backlog frontmatter ๅไธๅฏ่ง**ใ
- ่ฏทๅง็ป้่ฟ CLI ๅฝไปคๅๅปบ๏ผๆๅจๆไปถๅคดๅ ไธ `id`/`title`/`date`/`status` ็ญ frontmatterใ
- Documents ๆฏๆ `--type`๏ผreadme/guide/specification/other๏ผๅ `--tags`ใ
- Decisions ๆฏๆ `--status`๏ผProposed/Accepted/Deprecated/Superseded๏ผใ
EOF
backlog decision create "็คบไพ๏ผๆถๆๅณ็ญ่ฎฐๅฝๆจกๆฟ" --status "Proposed"
DECISION_FILE=$(ls backlog/decisions/decision-* 2>/dev/null | head -1)
if [ -n "$DECISION_FILE" ]; then
python3 - "$DECISION_FILE" <<'PYEOF'
import re, sys
path = sys.argv[1]
with open(path) as f:
content = f.read()
body = """
## Context
๏ผๆ่ฟฐๅฏผ่ด้่ฆๅๆญคๅณ็ญ็่ๆฏใ็บฆๆๆ้ฎ้ขใๅ
ๅซๅทฒ่่็ๅค้ๆนๆกใ๏ผ
## Decision
๏ผๆธ
ๆฐ้่ฟฐๆๅ็ๅณๅฎใ๏ผ
## Consequences
**ๆญฃ้ข๏ผ**
- ๏ผๆญคๅณ็ญๅธฆๆฅ็ๅฅฝๅค๏ผ
**่ด้ข / ็บฆๆ๏ผ**
- ๏ผๆ่กกใ้ๅถใๆ้่ฆๆณจๆ็ไบ้กน๏ผ
"""
content = re.sub(r'(## Context\n)\n(## Decision)', r'\1๏ผๆ่ฟฐ่ๆฏ๏ผ\n\n\2', content)
parts = content.split('---', 2)
if len(parts) == 3:
content = '---'.join(parts[:2]) + '---' + body
with open(path, 'w') as f:
f.write(content)
PYEOF
fi
echo "โ seeded example document and decision"
else
echo "โ docs/decisions not empty โ skipping seed"
fi
printSummary
echo ""
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo " backlog-setup complete"
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo ""
if [ ${#EXISTING[@]} -gt 0 ]; then
echo "Already present:"
for COL in "${EXISTING[@]}"; do echo " โ $COL"; done
fi
if [ ${#ADDED[@]} -gt 0 ]; then
echo "Added:"
for COL in "${ADDED[@]}"; do echo " + $COL"; done
fi
echo ""
echo "All required columns are ready."
echo ""
echo "Next steps:"
echo " 1. Create your first task:"
echo " /feature-to-backlog <feature description>"
echo ""
echo " 2. Move the task to Ready, then start the worker:"
echo " /loop-backlog"
echo ""
echo "โโโ Web UI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo " backlog browser"
echo ""
echo " TASKS โ tasks created by /feature-to-backlog, /task-to-backlog"
echo " DOCUMENTS โ guides & specs: backlog document create <title> --type guide"
echo " DECISIONS โ ADRs: backlog decision create <title> --status Accepted"
echo ""
echo " โ Files placed manually in backlog/docs/ or backlog/decisions/"
echo " are invisible in the web UI unless they have backlog frontmatter."
echo " Always use the CLI commands above."
Notes
- Statuses are managed by direct edit of
backlog/config.yml (Python regex rewrite);
backlog column add was removed in CLI v1.45+.
- The script is idempotent: running it multiple times only adds truly missing columns.
The seed step is also idempotent: skipped if
backlog/docs/ or backlog/decisions/
already contain any files.
backlog-setup auto-detects the project type and appends a ## L0 Config block to
CLAUDE.md (with human confirmation). The step is idempotent โ it skips if the block
already exists.
- Web UI content areas and their CLI commands:
- TASKS โ
backlog task create
- DOCUMENTS โ
backlog document create (stores in backlog/docs/)
- DECISIONS โ
backlog decision create (stores in backlog/decisions/)
- Manual
.md files without backlog frontmatter are invisible in the web UI.