215 lines
4.0 KiB
Bash
Executable File
215 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
LB_DIR="$ROOT_DIR/.loop-build"
|
|
STATE_DIR="$LB_DIR/state"
|
|
CONFIG_DIR="$LB_DIR/config"
|
|
TASKS_FILE="$LB_DIR/TASKS.md"
|
|
STATE_FILE="$STATE_DIR/current_task.json"
|
|
HISTORY_FILE="$STATE_DIR/history.ndjson"
|
|
|
|
need_approval_files=("$TASKS_FILE" "$STATE_FILE" "$HISTORY_FILE")
|
|
|
|
log() {
|
|
printf '%s\n' "$*"
|
|
}
|
|
|
|
check_dependencies() {
|
|
local missing=0
|
|
for dep in git bash rg jq; do
|
|
if ! command -v "$dep" >/dev/null 2>&1; then
|
|
printf '[init][MISSING] %s\n' "$dep"
|
|
printf ' install suggestion: use your package manager (e.g. apt, brew, or winget)\n'
|
|
missing=1
|
|
else
|
|
printf '[init][OK] %s\n' "$dep"
|
|
fi
|
|
done
|
|
if (( missing != 0 )); then
|
|
log "[init] dependency check found missing tools. init continues with warnings only."
|
|
fi
|
|
}
|
|
|
|
render_task_template() {
|
|
cat > "$TASKS_FILE" <<'EOF_TPL'
|
|
# loop-build Task Template
|
|
|
|
## Task
|
|
|
|
- task_id:
|
|
- goal:
|
|
- constraints:
|
|
- repo_context_hint:
|
|
- created_at:
|
|
- updated_at:
|
|
|
|
## Plan source
|
|
|
|
- generated_by: prompts/planner.md
|
|
- plan_status: not_started
|
|
- current_step:
|
|
- verify_targets:
|
|
|
|
## Progress
|
|
|
|
- completed_steps:
|
|
- next_step:
|
|
- open_questions:
|
|
- last_verification:
|
|
|
|
## Notes
|
|
|
|
- plan diff scope should stay <= 2 files/step
|
|
- each step should be one incremental change and one verification
|
|
- avoid full-file rewrites
|
|
EOF_TPL
|
|
}
|
|
|
|
init_state_file() {
|
|
if [[ -f "$STATE_FILE" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
cat > "$STATE_FILE" <<EOF_STATE
|
|
{
|
|
"task_id": "",
|
|
"goal": "",
|
|
"constraints": [],
|
|
"plan_steps": [],
|
|
"success_criteria": [],
|
|
"risk_notes": [],
|
|
"required_files": [],
|
|
"verify_targets": [],
|
|
"plan_status": "not_started",
|
|
"pending_action": "need_plan_approval",
|
|
"next_step": 1,
|
|
"completed_steps": [],
|
|
"open_questions": [],
|
|
"current_step": null,
|
|
"last_verification": null,
|
|
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
}
|
|
EOF_STATE
|
|
}
|
|
|
|
init_default_config() {
|
|
if [[ -f "$CONFIG_DIR/policy.env" ]]; then
|
|
return 0
|
|
fi
|
|
cat > "$CONFIG_DIR/policy.env" <<'EOF_CFG'
|
|
# Loop Build policy toggles
|
|
# Commands in denylist can execute only after explicit unlock + secondary confirm.
|
|
POLICY_SECONDARY_CONFIRM=0
|
|
ALLOW_SUDO=0
|
|
ALLOW_RM_RF=0
|
|
ALLOW_NETWORK=0
|
|
ALLOW_CURL_BASH=0
|
|
ALLOW_INSTALL=0
|
|
ALLOW_GIT_NETWORK=0
|
|
ALLOW_PROFILE_MODIFY=0
|
|
EOF_CFG
|
|
}
|
|
|
|
ensure_default_files() {
|
|
if [[ ! -f "$STATE_DIR/history.ndjson" ]]; then
|
|
: > "$HISTORY_FILE"
|
|
fi
|
|
|
|
if [[ ! -f "$TASKS_FILE" ]]; then
|
|
render_task_template
|
|
fi
|
|
|
|
if [[ ! -f "$CONFIG_DIR/allowlist.txt" ]]; then
|
|
cat > "$CONFIG_DIR/allowlist.txt" <<'EOF_ALLOW'
|
|
ls
|
|
ls -la
|
|
cat
|
|
rg
|
|
grep
|
|
tree
|
|
git status
|
|
git diff
|
|
git rev-parse
|
|
git log
|
|
git show
|
|
git branch
|
|
git branch --show-current
|
|
git status --short
|
|
git blame
|
|
sed
|
|
awk
|
|
head
|
|
tail
|
|
wc
|
|
printf
|
|
echo
|
|
find
|
|
pwd
|
|
false
|
|
true
|
|
EOF_ALLOW
|
|
fi
|
|
|
|
if [[ ! -f "$CONFIG_DIR/denylist.txt" ]]; then
|
|
cat > "$CONFIG_DIR/denylist.txt" <<'EOF_DENY'
|
|
sudo
|
|
rm -rf
|
|
rm -fr
|
|
git clone
|
|
git push
|
|
git pull
|
|
git commit
|
|
git rebase
|
|
git reset
|
|
curl
|
|
wget
|
|
npm install
|
|
npm i
|
|
yarn add
|
|
yarn install
|
|
pnpm add
|
|
pnpm install
|
|
pip install
|
|
pip3 install
|
|
composer install
|
|
bash -c
|
|
sh -c
|
|
curl | bash
|
|
wget | sh
|
|
EOF_DENY
|
|
fi
|
|
}
|
|
|
|
check_git_state_hint() {
|
|
if [[ -d "$ROOT_DIR/.git" ]] && command -v git >/dev/null 2>&1; then
|
|
changed=$(git -C "$ROOT_DIR" status --short | wc -l | tr -d ' ')
|
|
if [[ "$changed" != "0" ]]; then
|
|
log "[init][WARN] repository is not clean; recommended for safer incremental steps."
|
|
else
|
|
log "[init] repo clean check: passed"
|
|
fi
|
|
else
|
|
log "[init][WARN] no .git metadata detected; diff/rollback features are limited."
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
log "[init] starting loop-build bootstrap"
|
|
check_dependencies
|
|
mkdir -p "$STATE_DIR" "$CONFIG_DIR"
|
|
init_state_file
|
|
ensure_default_files
|
|
init_default_config
|
|
check_git_state_hint
|
|
|
|
log "[init] done."
|
|
log "[init] next steps:"
|
|
log " 1) complete .loop-build/state/current_task.json from a planner response"
|
|
log " 2) run ./ .loop-build/scripts/run_task.sh APPROVE_PLAN"
|
|
log " 3) run ./ .loop-build/scripts/run_task.sh step"
|
|
}
|
|
|
|
main "$@"
|