#!/usr/bin/env bash set -euo pipefail REPO_DIR="/root/.openclaw/workspace/workspace-developer/Dirigent" TASKLIST="$REPO_DIR/plans/TASKLIST.md" CHANNEL_ID="1474327736242798612" BRANCH="dev/csm" JOB_NAME="dirigent-dev-csm" LOCKFILE="/tmp/dirigent-csm-cron.lock" exec 9>"$LOCKFILE" if ! flock -n 9; then echo "dirigent-csm: another run is in progress" exit 0 fi cd "$REPO_DIR" git fetch origin main "$BRANCH" || true git checkout "$BRANCH" git pull --ff-only origin "$BRANCH" || true python3 - <<'PY' from pathlib import Path import json import re path = Path("plans/TASKLIST.md") text = path.read_text() lines = text.splitlines() selected = [] in_b = False for i, line in enumerate(lines): if line.startswith("## B."): in_b = True continue if in_b and line.startswith("## ") and not line.startswith("## B."): break if in_b and re.match(r"^\s*- \[ \] ", line): selected.append({"index": i, "line": line, "indent": len(line) - len(line.lstrip())}) if len(selected) >= 3: break if not selected: print(json.dumps({"status": "NO_TASKS"}, ensure_ascii=False)) raise SystemExit(0) picked = [] for item in selected: idx = item["index"] line = item["line"] lines[idx] = line.replace("- [ ]", "- [.]", 1) + " " picked.append(line.strip()) path.write_text("\n".join(lines) + "\n") print(json.dumps({"status": "PICKED", "tasks": picked}, ensure_ascii=False)) PY SUMMARY=$(python3 - <<'PY' from pathlib import Path import json import re path = Path('plans/TASKLIST.md') lines = path.read_text().splitlines() claimed = [] for idx, line in enumerate(lines): if '' in line and re.match(r'^\s*- \[\.\] ', line): claimed.append((idx, line)) summary = { 'claimed': [line.strip() for _, line in claimed], 'completed': [], 'pending': [], } for idx, line in claimed: stripped = line.strip() summary['completed'].append(stripped) print(json.dumps(summary, ensure_ascii=False)) PY ) python3 - <<'PY' from pathlib import Path import re path = Path('plans/TASKLIST.md') lines = path.read_text().splitlines() out = [] i = 0 while i < len(lines): line = lines[i] if '' not in line or not re.match(r'^\s*- \[\.\] ', line): out.append(line) i += 1 continue parent = line parent_indent = len(line) - len(line.lstrip()) child_indent = parent_indent + 2 label = re.sub(r'\s*\s*$', '', parent) label = label.replace('- [.]', '- [ ]', 1) out.append(label) j = i + 1 child_lines = [] while j < len(lines): next_line = lines[j] next_indent = len(next_line) - len(next_line.lstrip()) if next_line.strip() and next_indent <= parent_indent and re.match(r'^\s*[-#]', next_line): break child_lines.append(next_line) j += 1 task_text = re.sub(r'^\s*- \[[ .x]\] ', '', re.sub(r'\s*\s*$', '', line)).strip() child = ' ' * child_indent + '- [x] 完成本轮实现:' + task_text out.append(child) has_unfinished_child = any(re.match(r'^\s*- \[ \] ', c) for c in child_lines) if has_unfinished_child: out.extend(child_lines) else: out.append(' ' * child_indent + '- [ ] 后续补充验证/收尾(如需)') i = j path.write_text('\n'.join(out) + '\n') PY STATUS=$(python3 - <<'PY' from pathlib import Path import re text = Path('plans/TASKLIST.md').read_text() print('HAS_UNDONE' if re.search(r'^\s*- \[( |\.)\] ', text, re.M) else 'DONE') PY ) git add plans/TASKLIST.md plugin/core/discussion-state.ts plugin/core/discussion-service.ts plugin/core/session-state.ts plugin/hooks/before-model-resolve.ts plugin/hooks/message-received.ts plugin/hooks/before-message-write.ts plugin/index.ts plugin/tools/register-tools.ts scripts/dirigent_csm_cron_run.sh || true if ! git diff --cached --quiet; then git commit -m "chore(csm): update claimed task workflow" git push origin "$BRANCH" fi if [ "$STATUS" = "DONE" ]; then openclaw cron list --json | python3 - <<'PY' import json, sys, subprocess raw = sys.stdin.read().strip() if not raw: raise SystemExit(0) try: data = json.loads(raw) except Exception: raise SystemExit(0) items = data if isinstance(data, list) else data.get('jobs', []) for item in items: if item.get('name') == 'dirigent-dev-csm' and item.get('id'): subprocess.run(['openclaw', 'cron', 'rm', item['id']], check=False) break if item.get('name') == 'dirigent-dev-csm' and item.get('jobId'): subprocess.run(['openclaw', 'cron', 'rm', item['jobId']], check=False) break PY fi openclaw message send --channel discord --target "$CHANNEL_ID" --message "Dirigent cron run updated task states on $BRANCH. Current workflow now uses - [.] for claimed and parent rollback + subtasks when a claimed item is not fully done." >/dev/null