بنقرة واحدة
Resync local plan state from GitHub issue/PR states
npx skills add https://github.com/MockaSort-Studio/hall-of-automata-cli --skill hall-reconcileانسخ والصق هذا الأمر في Claude Code لتثبيت المهارة
Resync local plan state from GitHub issue/PR states
npx skills add https://github.com/MockaSort-Studio/hall-of-automata-cli --skill hall-reconcileانسخ والصق هذا الأمر في Claude Code لتثبيت المهارة
Enter Old Major session mode — fetch personas, assemble session stack, activate
Dispatch ready tasks to the Hall as GitHub Issues with quota stewardship
Dump the current plan as JSON, Markdown, and/or Mermaid diagram
Provision the Hall of Automata Projects v2 board, custom fields, and labels on the current target repo — idempotent
List, view, or prune saved Tier-2 subagent consultation outputs
Clean up old plan directories or stale persona cache from .hall-cache/
| name | hall-reconcile |
| description | Resync local plan state from GitHub issue/PR states |
| allowed-tools | ["Bash","Read","Write","CronDelete"] |
Resync the local plan with GitHub's current state. Runs automatically before any dispatch; can be invoked manually.
If .hall-cache/watcher-events.jsonl exists and is non-empty:
"Watcher detected N events since last reconcile: [list]"> .hall-cache/watcher-events.jsonlIf absent or empty, skip silently.
Find the active plan. For each task with a github_issue number:
PLAN_DIR=$(ls -d .hall-cache/plans/*/ | sort | tail -1)
Read repo from $PLAN_DIR/plan.json for the --repo argument throughout: REPO=$(python3 -c "import json; print(json.load(open('$PLAN_DIR/plan.json'))['repo'])") — split into ORG and REPO parts as needed.
BOARD_ACTIVE=$(python3 -c "import json; print(bool(json.load(open('.hall-cache/session/config.json')).get('board_project_number','')))"\ 2>/dev/null || echo "False")
For each issue, call issue_read (method: get, owner: ORG, repo: REPO, issue_number: N).
On rate_limit error, fall back to:
gh api repos/{ORG}/{REPO}/issues/{N} --jq '{state:.state,labels:[.labels[].name]}'
For any issue with state = closed, additionally check for a linked PR:
Call search_pull_requests (query: repo:{ORG}/{REPO} closes #{N}).
On rate_limit error, fall back to:
gh pr list --repo {ORG}/{REPO} --search "closes #{N}" --json state,mergedAt
Update task status using this table:
| GitHub state | Condition | → Plan status |
|---|---|---|
| open | hall:in-progress | IN_PROGRESS |
| open | hall:awaiting-input | AWAITING_INPUT |
| open | hall:post-mortem | FAILED |
| open | hall:invoker-queued | DISPATCHED (queued) |
| closed | linked PR exists and is open (not merged) | REVIEWING |
| closed | linked PR merged | MERGED |
| closed | no linked PR | FAILED |
A PR is open if state = "open". A PR is merged if mergedAt is non-null (use this to distinguish REVIEWING from MERGED when a PR is closed).
If a PR associated with a MERGED issue has its own merged_at value, record it in the task entry.
After updating all tasks, identify any newly-eligible tasks (tasks whose depends_on entries are all now MERGED) and update them from PLANNED to READY (deferred).
needs_reviewOpen PR detection: For each task with status DISPATCHED or IN_PROGRESS that has a github_issue and does NOT already have needs_review: true:
PR_INFO=$(gh pr list --repo {ORG}/{REPO} --search "closes #{N} is:open" --json number,headSha --jq '.[0]')
If the result is non-empty and non-null: set github_pr to the PR number (if not already set or changed); set needs_review: true and review_cycle: 1 on the task entry.
Fix-commit detection: For each task with github_pr set, review_cycle >= 1, and needs_review: false:
HEAD_SHA=$(gh pr view {github_pr} --repo {ORG}/{REPO} --json headRefOid --jq '.headRefOid')
If HEAD_SHA differs from task["last_reviewed_sha"] (and last_reviewed_sha is non-empty): set needs_review: true.
Newly REVIEWING: Determine which tasks newly transitioned into REVIEWING — status was not REVIEWING on the prior reconcile pass, is now REVIEWING. For each such task:
automation_level from .hall-cache/session/config.json. If the file is absent, treat as 0.automation_level >= 1, set needs_review: true on that task in plan.json.automation_level is 0 or the file is absent, do not write needs_review (or write false).Reconcile must not clear needs_review — only dispatch clears it after filing the review issue.
AUTOMATION_LEVEL=$(python3 -c "
import json, sys
try:
cfg = json.load(open('.hall-cache/session/config.json'))
print(cfg.get('automation_level', 0))
except FileNotFoundError:
print(0)
")
Write the updated plan.json.
After writing plan.json, check if all tasks across all plans have reached a terminal state:
ALL_DONE=$(python3 -c "
import json, glob
all_tasks = [t for f in glob.glob('.hall-cache/plans/*/plan.json') for t in json.load(open(f)).get('tasks', [])]
terminal = {'MERGED', 'DONE', 'FAILED', 'ESCALATED'}
print('true' if all_tasks and all(t['status'] in terminal for t in all_tasks) else 'false')
")
If ALL_DONE=true and .hall-cache/session/cron.json exists:
CRON_ID=$(python3 -c "import json; print(json.load(open('.hall-cache/session/cron.json'))['cron_id'])" 2>/dev/null || echo "")
If CRON_ID is non-empty: call CronDelete with id=$CRON_ID. Then:
rm -f .hall-cache/session/cron.json
echo "All tasks terminal — autonomous cron cancelled."
If GitHub wins on any conflict (task shows MERGED on GitHub but DISPATCHED locally), report the discrepancy and apply the GitHub state.
If BOARD_ACTIVE=False, skip this section entirely.
Fetch invoker login once before the loop:
INVOKER_LOGIN=$(gh api /user --jq '.login')
For each task whose status newly transitioned to MERGED or DONE during this pass:
Find item in board.json where issue_number matches task["github_issue"]; if absent, log Board item not found for issue #N and skip.
Resolve the "Done" option ID from board-meta.json["fields"]["Status"]["options"] (entry with name "Done").
Call update_item_field:
project_id = board.json["project_id"]item_id = matched item idfield_id = board-meta.json["fields"]["Status"]["id"]value = {"singleSelectOptionId": <Done option ID>}invoker_login = $INVOKER_LOGINOn rate_limit/secondary-rate-limit error:
gh api graphql -f query='mutation { updateProjectV2ItemFieldValue(input: { projectId: "<project_id>", itemId: "<item_id>", fieldId: "<field_id>", value: { singleSelectOptionId: "<option_id>" } }) { projectV2Item { id } } }'
Log Board item #<N> → Done on success; log error and continue — never abort reconcile.
Only process tasks present in plan.json; skip board-only items (OKR/KR).
End with a reconciliation summary:
N tasks updated, M newly eligible
If any tasks newly transitioned into REVIEWING, append on a new line:
K tasks newly REVIEWING — review dispatch pending
Omit the second line if K = 0.