Use when human-implemented commits need to be mapped back to planned tasks. Reconciles implementation.md and state.md after manual work outside the OAT workflow.
Bridge the gap between human implementation and OAT's artifact-driven workflow. Analyzes commits made outside the structured OAT flow, maps them to planned tasks, and updates tracking artifacts after human confirmation.
Required:
plan.md containing task definitionsimplement phase (or plan phase with oat_phase_status: complete)implementation.mdOAT MODE: Reconciliation
Purpose: Analyze manual/human commits, map them to planned tasks, and reconcile tracking artifacts with user confirmation.
Print a phase banner once at start:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ OAT ▸ RECONCILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
For each step, announce a compact header:
OAT ▸ RECONCILE — Step N: {step_name}Before multi-step bookkeeping:
[1/N] {action}…Keep it concise; don't print a line for every shell command.
BLOCKED Activities:
ALLOWED Activities:
Self-Correction Protocol: If you catch yourself:
Recovery:
OAT stores active project context in .oat/config.local.json (activeProject, local-only).
PROJECT_PATH=$(oat config get activeProject 2>/dev/null || true)
PROJECTS_ROOT="${OAT_PROJECTS_ROOT:-$(oat config get projects.root 2>/dev/null || echo ".oat/projects/shared")}"
PROJECTS_ROOT="${PROJECTS_ROOT%/}"
If PROJECT_PATH is missing/invalid:
{project-name}PROJECT_PATH to ${PROJECTS_ROOT}/{project-name}mkdir -p .oat
oat config set activeProject "$PROJECT_PATH"
If PROJECT_PATH is valid: derive {project-name} as the directory name (basename of the path).
Verify the project is ready for reconciliation:
Check plan.md exists:
test -f "$PROJECT_PATH/plan.md" || { echo "ERROR: plan.md not found. Run oat-project-plan first."; exit 1; }
Check project phase:
PHASE=$(grep "^oat_phase:" "$PROJECT_PATH/state.md" 2>/dev/null | awk '{print $2}')
PHASE_STATUS=$(grep "^oat_phase_status:" "$PROJECT_PATH/state.md" 2>/dev/null | awk '{print $2}')
PHASE is implement: proceedPHASE is plan and PHASE_STATUS is complete: proceed (plan just finished, implementation starting)Check for untracked commits:
implementation.md if it exists — find the last recorded commit SHAIdentify the last commit that OAT has already tracked. Everything after this commit is "untracked human work" that needs reconciliation.
Priority 1 — Last tracked commit in implementation.md:
Read implementation.md and find the last task entry with a commit SHA (look for **Commit:** {sha} patterns where sha is not - or empty). This is the most reliable checkpoint because it's exactly what OAT already recorded.
LAST_TRACKED_SHA=$(grep -oP '\*\*Commit:\*\*\s+\K[0-9a-f]{7,40}' "$PROJECT_PATH/implementation.md" | tail -1)
Priority 2 — Last OAT-convention commit in git log:
If implementation.md has no tracked commits, scan git log for the last commit matching OAT patterns:
# Task commits: feat(p01-t01): ..., fix(p02-t03): ...
OAT_TASK_SHA=$(git log --oneline --grep='(p[0-9]*-t[0-9]*)' --extended-regexp -n 1 --format='%H')
# Bookkeeping commits: chore(oat): update tracking artifacts ...
OAT_BOOK_SHA=$(git log --oneline --grep='chore(oat):' -n 1 --format='%H')
# Use whichever is more recent (closer to HEAD)
if [ -n "$OAT_TASK_SHA" ] && [ -n "$OAT_BOOK_SHA" ]; then
# Compare: is OAT_TASK_SHA an ancestor of OAT_BOOK_SHA?
if git merge-base --is-ancestor "$OAT_TASK_SHA" "$OAT_BOOK_SHA" 2>/dev/null; then
CHECKPOINT="$OAT_BOOK_SHA"
else
CHECKPOINT="$OAT_TASK_SHA"
fi
elif [ -n "$OAT_TASK_SHA" ]; then
CHECKPOINT="$OAT_TASK_SHA"
elif [ -n "$OAT_BOOK_SHA" ]; then
CHECKPOINT="$OAT_BOOK_SHA"
fi
Priority 3 — Merge-base fallback:
If no OAT commits are found at all, fall back to the merge-base with the default branch:
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
DEFAULT_BRANCH="${DEFAULT_BRANCH:-main}"
CHECKPOINT=$(git merge-base HEAD "$DEFAULT_BRANCH" 2>/dev/null)
If merge-base also fails (e.g., orphan branch), use the first commit on the branch:
CHECKPOINT=$(git rev-list --max-parents=0 HEAD | tail -1)
Report checkpoint to user:
OAT ▸ RECONCILE — Step 1: Checkpoint
Last tracked commit: {CHECKPOINT_SHA} ({date})
Source: {implementation.md | git log pattern | merge-base}
Task: {last_tracked_task_id or "pre-project"}
Commits since checkpoint: {count}
Count commits in range:
COMMIT_COUNT=$(git rev-list --count "$CHECKPOINT"..HEAD)
User confirmation gate:
Ask user: "Use this as the checkpoint? Or provide a different commit SHA."
If user provides an alternative SHA, validate it:
git cat-file -t "$USER_SHA" 2>/dev/null | grep -q commit
Store the confirmed checkpoint as $CHECKPOINT for use in subsequent steps.
Gather all commits between the checkpoint and HEAD, filter out noise, and extract metadata for mapping.
Collect commits in range:
git log --format='%H|%s|%an|%ai' "$CHECKPOINT"..HEAD --reverse
This gives oldest-first ordering (matches plan execution order). Parse each line into: SHA, message, author, date.
Gather per-commit details:
For each commit SHA, collect:
Changed files:
git diff-tree --no-commit-id -r --name-only "$SHA"
Diff stats (insertions/deletions):
git diff --stat "$SHA"~1.."$SHA" 2>/dev/null || git diff --stat "$(git hash-object -t tree /dev/null)".."$SHA"
(The fallback handles the case where $SHA is the first commit on the branch.)
Filter out non-implementation commits:
Remove from the analysis set:
Merge commits:
git rev-list --merges "$CHECKPOINT"..HEAD
Any SHA in this list is excluded.
Bookkeeping-only commits — commits where ALL changed files match OAT tracking patterns:
implementation.md, state.md, or plan.md within any .oat/ pathstate.md within any .oat/ pathchore(oat):Rule: if every file in the commit is under a .oat/ subdirectory and matches one of implementation.md, state.md, plan.md, or discovery.md — then it's bookkeeping and should be excluded.
Already-tracked commits — commits whose SHA already appears in implementation.md task entries (from Step 0.5 check).
Present commit summary to user:
OAT ▸ RECONCILE — Step 2: Commit Analysis
Range: {CHECKPOINT_SHORT}..HEAD ({total_count} commits, {filtered_count} after filtering)
Filtered out: {merge_count} merges, {bookkeeping_count} bookkeeping, {tracked_count} already tracked
| # | SHA (short) | Message (first 60 chars) | Files | Insertions | Deletions |
|----|-------------|-----------------------------------|-------|------------|-----------|
| 1 | abc1234 | add auth endpoint | 3 | +120 | -5 |
| 2 | def5678 | fix validation logic | 1 | +15 | -8 |
| .. | ... | ... | ... | ... | ... |
Extract plan tasks for mapping:
Read plan.md and extract all tasks:
### Task pNN-tNN: {Name} headers to get task IDs and names**Files:** blocks under each task to get expected file lists (both Create: and Modify: entries)completed status in implementation.md (skip these during mapping)Store as a structured list:
TASKS = [
{ id: "p01-t01", name: "...", files: ["path/a.ts", "path/b.ts"], keywords: ["auth", "endpoint"] },
{ id: "p01-t02", name: "...", files: [...], keywords: [...] },
...
]
Only include tasks that are not yet completed in implementation.md.
For each filtered commit from Step 2, attempt to map it to a planned task using signals in priority order. Once a commit is mapped via a higher-priority signal, skip lower-priority signals for that commit.
Signal A — Task ID in commit message (→ High confidence):
Check if the commit message contains a task ID pattern:
# Match patterns like: feat(p01-t03): ..., fix(p02-t01): ..., (p01-t02)
TASK_ID=$(echo "$COMMIT_MSG" | grep -oP 'p[0-9]+-t[0-9]+' | head -1)
If $TASK_ID matches a pending task in the plan: map with confidence=high.
Signal B — File overlap (→ High/Medium/Low confidence):
For each unmatched commit, compare its changed files against each pending task's file list:
For each pending task T:
task_files = set of files listed in plan for T
commit_files = set of files changed in this commit
intersection = commit_files ∩ task_files
overlap_ratio = |intersection| / |task_files| (if task_files is non-empty)
Classification:
overlap_ratio ≥ 0.8 AND only one task matches at this level → confidence=high
overlap_ratio ≥ 0.4 → confidence=medium
overlap_ratio > 0 → confidence=low
Pick the best match (highest overlap_ratio). Break ties by plan order (earlier task wins).
If a commit's files don't appear in any task's file list, proceed to Signal C.
Signal C — Message keyword match (→ Medium confidence):
For each still-unmatched commit:
If matching_tokens ≥ 2 with a single task → confidence=medium
If matching_tokens ≥ 2 with multiple tasks → confidence=low (ambiguous)
Signal D — Temporal ordering (→ Low confidence):
For each still-unmatched commit, use its position in the commit sequence as a tiebreaker:
Sort remaining unmatched commits by commit date (oldest first).
Sort remaining pending tasks by plan order (p01-t01, p01-t02, ...).
For each unmatched commit (in date order):
candidate_task = first pending task (in plan order) that has no commits mapped yet
If candidate_task exists:
map commit → candidate_task with confidence=low
Remove candidate_task from pending list
Else:
classify as unmapped
This signal is intentionally conservative (low confidence) — it only applies when no stronger signal matched. It is most useful for ambiguous manual ranges where commit order is the only remaining signal that correlates with plan task order.
Signal E — No match (→ Unmapped):
Any commits still unmatched after all signals: classify as unmapped.
Multi-commit grouping:
After individual mapping, group commits that map to the same task:
For each task with multiple mapped commits:
- representative_sha = latest commit SHA (most recent)
- combined_files = union of all commit file lists
- combined_message = concatenation of commit messages (for outcome generation)
- confidence = lowest confidence among grouped commits (conservative)
Present mapping report:
OAT ▸ RECONCILE — Step 3: Mapping Report
Mapped to tasks:
| Task | Task Name | Commits | Confidence | Files |
|-----------|--------------------------|------------------|------------|-------|
| p01-t03 | Add validation logic | abc1234, def5678 | high | 4 |
| p02-t01 | Implement API endpoint | ghi9012 | medium | 2 |
Unmapped commits:
| SHA (short) | Message | Files |
|-------------|----------------------------|-------|
| jkl3456 | update readme | 1 |
| mno7890 | fix typo in config | 1 |
Tasks still pending (no commits matched):
| Task | Task Name |
|-----------|--------------------------|
| p02-t02 | Add integration tests |
Summary: {mapped_task_count}/{total_pending_tasks} tasks addressed,
{mapped_commit_count}/{total_commits} commits mapped,
{unmapped_count} unmapped
Present mappings to the user for confirmation. No artifact writes happen in this step — only building the confirmed mapping set.
4a. High-confidence batch approval:
Present all high-confidence mappings as a batch:
The following high-confidence mappings were detected:
p01-t03 "Add validation logic"
← abc1234: "add input validation" (file overlap 90%)
← def5678: "fix validation edge case" (file overlap 85%)
p02-t01 "Implement API endpoint"
← ghi9012: "add /api/users endpoint" (task ID in message)
Accept all high-confidence mappings?
1. Yes, accept all
2. Review individually
If user chooses "Review individually", fall through to the per-mapping flow below.
4b. Medium/Low-confidence individual review:
For each medium or low confidence mapping, present individually:
Commit ghi9012: "fix validation logic" (2 files changed: src/validate.ts, src/utils.ts)
Best match: p02-t01 "Add input validation" (confidence: medium, file overlap 60%)
What should we do with this commit?
1. Accept mapping to p02-t01
2. Assign to a different task
3. Mark as unplanned work (log in implementation.md)
4. Skip (don't log this commit)
If user chooses "Assign to a different task", present the list of unmatched pending tasks and let them pick.
4c. Unmapped commits:
For each unmapped commit:
Commit jkl3456: "update readme" (1 file changed: README.md)
No matching task found.
What should we do with this commit?
1. Assign to a task: [list pending tasks]
2. Log as unplanned work in implementation.md
3. Skip (don't log)
4d. Task completion status:
For each task that has confirmed mapped commits, ask about completion:
Task p02-t01 "Add input validation" — 1 commit mapped.
Mark this task as:
1. Completed (all work for this task is done)
2. In Progress (partial — more work still needed)
Default to "Completed" for high-confidence mappings; default to "In Progress" for medium/low.
4e. Final confirmation:
Present the complete confirmed mapping before proceeding:
OAT ▸ RECONCILE — Step 4: Confirmed Mappings
Tasks to update:
| Task | Status | Commits | Confidence |
|-----------|-------------|------------------|------------|
| p01-t03 | completed | abc1234, def5678 | high |
| p02-t01 | in_progress | ghi9012 | medium |
Unplanned work to log:
| SHA | Summary |
|-----------|--------------------------|
| jkl3456 | update readme |
Skipped commits: mno7890
Proceed with artifact updates?
1. Yes, update artifacts
2. Go back and revise mappings
Only proceed to Step 5 after user confirms "Yes, update artifacts".
Apply the confirmed mappings to OAT tracking artifacts. This is the only step that writes files.
5a. Read existing implementation.md:
Read the full file. Identify:
### Task pNN-tNN: section)5b. Write task entries in implementation.md:
For each confirmed task mapping, check whether a ### Task {task_id}: section already exists in implementation.md:
**Reconciliation Update:** block below the existing entry to augment it with the reconciled data.New entry template (when no existing section):
### Task {task_id}: {Task Name}
**Status:** {completed | in_progress}
**Commit:** {representative_sha} (reconciled)
**Outcome (reconciled from manual implementation):**
- {2-5 bullets derived from commit messages and diff summary}
- {Use git show --stat and commit messages to infer what changed}
**Files changed:**
- `{path}` - {inferred purpose from diff context}
**Verification:**
- Run: `{verification command from plan task, if available}`
- Result: not verified — reconciled entry
**Notes / Decisions:**
- Reconciled from manual implementation on {today's date}
- Original commits: {comma-separated SHA list}
- Mapping confidence: {high|medium|low}
- Mapping signal: {task ID in message | file overlap N% | keyword match}
Augmentation template (when section already exists — append below existing content):
**Reconciliation Update ({today's date}):**
- Additional commits mapped: {comma-separated SHA list}
- Mapping confidence: {high|medium|low}
- Mapping signal: {task ID in message | file overlap N% | keyword match}
- Additional files: {files not already listed, if any}
- Status updated: {if status changed, e.g., in_progress → completed}
Important: Never delete, replace, or overwrite existing task entry content. Existing notes, decisions, and outcomes represent logged history that must be preserved.
To generate the Outcome bullets:
git show --stat {sha} and git log --format='%s' {sha} -1 as source materialTo generate Files changed:
5c. Add unplanned work entries:
For each commit the user chose to log as unplanned work, append after the last task entry in the relevant phase (or at the end of the last phase):
### Unplanned: {commit message summary (first 60 chars)}
**Status:** completed
**Commit:** {sha} (unplanned)
**Outcome:**
- {1-3 bullets derived from commit message and diff}
**Files changed:**
- `{path}` - {inferred from diff}
**Notes:**
- Not part of original plan. Logged during reconciliation on {today's date}.
5d. Update progress table:
Recalculate the ## Progress Overview table dynamically from the project's actual data:
plan.md by scanning ## Phase N: headings### Task pNN-tNN: headers where NN is the phase numberimplementation.md entries with **Status:** completed| Phase | Status | Tasks | Completed |
| ----- | ------ | ----- | --------- |
{for each phase from plan.md:}
| Phase {N} | {status} | {task_count} | {completed_count}/{task_count} |
**Total:** {total_completed}/{total_tasks} tasks completed
A phase is complete when all its tasks are completed. A phase is in_progress when at least one task is completed or in_progress. Otherwise pending.
Do not hardcode phase counts or task totals — always derive them from the current project's plan and implementation artifacts.
5e. Update frontmatter:
In implementation.md:
oat_current_task_id: { next_pending_task_id } # or null if all complete
oat_last_updated: { today }
Find the next pending task by scanning plan order: first task with status not completed.
In state.md:
oat_current_task: { same as oat_current_task_id above }
oat_last_commit: { most recent reconciled commit SHA }
oat_phase: implement
oat_phase_status: in_progress
oat_project_state_updated: '{ISO 8601 UTC timestamp}'
# Always in_progress after reconciliation — only oat-project-review-receive
# may advance to complete after the final review passes.
5f. Append to Implementation Log:
Add a reconciliation session entry to the ## Implementation Log section:
### {today's date}
**Session Start:** reconciliation
{For each reconciled task:}
- [x] {task_id}: {task_name} - {sha} (reconciled, confidence: {level})
{For each unplanned entry:}
- [x] unplanned: {summary} - {sha}
**What changed (high level):**
- Reconciled {N} manually-implemented tasks from {M} commits
- Logged {K} unplanned work entries
**Decisions:**
- Mapping confidence breakdown: {high_count} high, {medium_count} medium, {low_count} low
- Skipped commits: {skip_count}
**Session End:** reconciliation complete
Create a single bookkeeping commit for all artifact updates and present a final summary.
6a. Stage only tracking files:
git add "$PROJECT_PATH/implementation.md" "$PROJECT_PATH/state.md"
# Only add plan.md if it was modified
git diff --name-only "$PROJECT_PATH/plan.md" 2>/dev/null | grep -q . && git add "$PROJECT_PATH/plan.md"
Important: Do NOT use git add -A or glob patterns. Only stage the specific OAT tracking files listed above.
6b. Commit with reconciliation message:
git diff --cached --quiet || git commit -m "chore(oat): reconcile manual implementation ({first_task_id}..{last_task_id})"
Example: chore(oat): reconcile manual implementation (p01-t03..p02-t01)
If only unplanned work was logged (no task mappings): chore(oat): reconcile unplanned manual implementation
6c. Refresh dashboard (optional):
oat state refresh 2>/dev/null || pnpm run cli -- state refresh 2>/dev/null || true
Dashboard refresh is best-effort; do not fail the reconciliation if it's unavailable.
6d. Print final summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OAT ▸ RECONCILE — Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Reconciled: {N} tasks from {M} commits
High confidence: {high_count}
Medium confidence: {medium_count}
Low confidence: {low_count}
Unplanned work logged: {K} entries
Commits skipped: {skip_count}
Tasks still pending: {pending_count}
Next task: {next_task_id} "{next_task_name}" (or "all tasks complete")
Tracking commit: {bookkeeping_sha}
Recommended next steps:
- oat-project-implement → continue with remaining tasks
- oat-project-review-provide → review all changes (including reconciled)
- oat-project-progress → check overall project status
oat_current_task_id, oat_current_task) are consistent across artifactsoat-project-progress, oat-project-review-provide) produce correct results after reconciliation