Summary
Two distinct branch concepts: mainBranch (config) is canonical codebase truth used
for sync/exploration/freshness. homeBranch (pipeline state) is where a specific
pipeline's work merges back to, set at seal time. In worktrees, homeBranch depends
on whether currentBranch matches the worktree's configured home: if yes →
mainBranch (standard flow), if no → worktreeConfig.homeBranch
(sub-branch merges back to worktree home).
How it works
Set at seal time: /pipe-seal stores homeBranch in
pipeline state. The value depends on context.
- Main worktree, on main:
homeBranch = mainBranch (standard). - Main worktree, on non-main branch (e.g.,
hotfix/v0.1.1): User
gets 4 options — hotfix-feature (local merge), hotfix-feature+review, feature PR targeting
currentBranch, or feature PR targeting mainBranch.
- Worktree, on its configured homeBranch:
homeBranch = config.mainBranch.
The worktree branch is where you work FROM, not where you merge TO.
- Worktree, on a sub-branch:
homeBranch = worktreeConfig.homeBranch.
Sub-branch merges back to the worktree's home.
Detection is deterministic — no user prompt. Seal compares
currentBranch to worktreeConfig.homeBranch. If
getWorktreeConfig fails with not-found (new worktree, no config yet), default
to config.mainBranch. On any other infrastructure failure, log a warning and
default to config.mainBranch.
Phase fallback rules: Recoverable phases (git-prepare, branch-create,
need-fixture-update, pr-create) fall back to config.mainBranch with a stderr
warning. Irreversible phases (merge-to-parent) require homeBranch and fail hard
if missing — a silent fallback could merge+push to the wrong branch.
| Uses homeBranch (pipeline state) | Uses mainBranch (config) |
| git-prepare (rebase target) | sync (pull canonical) |
| branch-create (branch from) | check-map-cursor (freshness) |
| need-fixture-update (diff base) | codemap-update (cursor) |
| pr-create (PR base) | exploration baseline |
| merge-to-parent (merge target) | |
| branch-cleanup (return to) | |
Worktree branch deletion gotcha
In worktrees, git branch -d may fail with "not fully merged" because: (1)
fetchOrigin() with fetch.prune=true removes the stale tracking ref,
(2) without an upstream, -d falls back to checking against HEAD, (3) HEAD is
homeBranch, not mainBranch where the PR merged. Fix: when -d
fails, verify the branch is merged into origin/mainBranch via
git merge-base --is-ancestor, then force-delete with -D. Only escalates
after cryptographic verification.
Rationale
The original design assumed all work merges to mainBranch. This broke when versioning
introduced hotfix branches with tag-based deploys: a client-reported issue grew from hotfix
into feature needing the full pipeline, but the pipeline couldn't target the hotfix branch.
The fix separates "where is canonical truth" (mainBranch) from "where does THIS
work go" (homeBranch). Worktrees were initially handled with a blanket
homeBranch = currentBranch, which caused PRs from worktrees to target the worktree
branch instead of main. Fixed by comparing currentBranch to the worktree's
configured home.
Alternatives considered
--home-branch CLI arg per phase. Fragile — orchestrator must pass conditionally, silent fallback if forgotten. Pipeline state already available via --task. - Separate
mainBranch override in config. mainBranch is canonical truth, shouldn't vary per pipeline. - Always create PRs (even hotfix). Overhead for hotfix where speed matters.
- Always set
homeBranch = currentBranch in worktrees. Caused PRs from worktrees to merge into the worktree branch instead of main. - Always force-delete with
-D in branch-cleanup. Unsafe — would silently delete unmerged branches. - Use
git fetch --prune=false. Workaround, not a fix. Leaves stale remote refs accumulating.
Source files
bin/commands/branch-cleanup.mjs · implements bin/commands/git-prepare.mjs · implements bin/commands/branch-create.mjs · implements bin/commands/need-fixture-update.mjs · implements bin/commands/merge-to-parent.mjs · implements skills/pr-create/SKILL.md · implements commands/pipe-seal.md · references skills/branch-cleanup/SKILL.md · documents
Relationships
Summary
Module maps (per-module detail stored on MCP) were killed during the synth rework. Too expensive
to produce (~15min synthesis), agents ignored them in practice. Per-module navigation detail now
lives in project agents, which agents already load.
Why it was killed
Two independent failures made module maps net-negative.
Production cost was prohibitive. Module map synthesis took ~15 minutes per
pipeline run. The synthesise-project-knowledge agent would scan files, run bash
commands, and build per-module detail — costing more than the broad codebase exploration it was
supposed to replace.
Consumers ignored them. Despite explicit instructions to call
getModuleMap(moduleName) before reading files, sub-agents skipped the call and
explored the codebase directly. Observed across multiple production runs. The instruction was
a soft suggestion agents could skip — unlike project agent files which agents read because
they're injected into their own prompt.
Injection was impossible. The alternative — injecting module maps into
sub-agent prompts — was rejected because the orchestrator would need to load them first. For
5-7 chunks, repeating the same module map data would stuff ~30% of the orchestrator's context.
How per-module detail works now
Per-module navigation detail lives in project agents
(config.agents.{type}.file). Sub-agents already read their own agent file —
this is the one instruction they reliably follow. Loaded once per sub-agent (not repeated across
chunks), already in the right context, zero orchestrator context cost.
Rationale
Module maps failed on both sides of the value equation: production was too expensive (~15min
synthesis per run, agent going off-script reading files and running bash) and consumption was
zero (agents ignored the instruction to call getModuleMap and explored files
directly). Project agents solve the same problem without these costs. The simplification also
removes a maintenance burden — one less MCP data type to keep current.
Alternatives considered
- Inject module maps into sub-agent prompts. Orchestrator must load to inject. ~30% of context across 5-7 chunks — exactly what context trimming eliminated.
- Enforce agent consumption (validate
moduleMapsLoaded). Hacky enforcement; doesn't solve the ~15min synthesis cost. - Cheaper shell-based synthesis (no LLM). Module map content requires judgment. Shell scripts produce low-quality maps that agents still ignore.
Relationships
post-merge-agents · part-of
Summary
Pipeline coordinators (pipe orchestrator, implement skill) must
minimise context accumulation. Strategies: delegate context loading to sub-agents, use atomic
MCP updates, skip redundant verification, enforce minimal returns, drop results after status
check.
Why it exists
The /pipe orchestrator runs inline — all phase dispatches, results, state updates,
and narration accumulate in a single context window. For a 7-chunk implement phase, this easily
reaches 50k+ tokens of coordinator overhead before review and test phases even start. An early
production incident showed two 18min+ stalls caused by context bloat from verbose sub-agent
returns.
The six principles
- Sub-agents own their context loading. Don't read files or MCP data into the
coordinator just to copy into sub-agent prompts. Tell sub-agents WHERE to load. Coordinator
pays ~50 tokens per path reference; sub-agent pays full cost in its own window.
- Atomic MCP updates over full-state writes. Chunk completion tracked by
artifact existence —
saveChunkArtifact + presence check, not savePipelineProgress.
- Trust sub-agent results. After a chunk completes, check Task status. Don't
re-fetch from MCP or spot-check files. The test phase catches real issues.
- Minimal logging. One line per chunk: "Chunk N (name) done." Don't restate
the full summary.
- Enforce minimal returns from all sub-agents. Every prompt ends with a
minimal-return template. Implement: status, chunk name, file count. Light review: status,
findings counts. Heavy review: JSON severity summary. Etc.
- Drop results after status check. Orchestrator discards sub-agent responses
after extracting state propagation data. Prevents accumulation across 15+ phases.
Key gotchas
- Context is monotonically increasing — everything stays forever until compaction.
- Implement runs as inline skill for user visibility, accepting the trade-off.
- Sub-agent Task results auto-enter the coordinator's context — minimal-return templates limit damage to ~50-100 tokens per phase.
- The orchestrator must NOT load
getPlan — only planExists for validation.
Rationale
Context limits are the primary scaling constraint for pipeline execution. The early production
incident demonstrated the failure mode: verbose sub-agent returns accumulated across phases,
causing two 18min+ stalls. Every optimisation that shifts cost from coordinator to sub-agents
directly extends capacity. The lean-coordinator approach preserves user visibility while
managing the budget.
Alternatives considered
- Run implement as an agent phase for full context isolation. User loses all progress visibility — one spinner for 30+ minutes.
- Use
/compact between chunks. Compaction loses in-memory state. Reload cost is itself expensive. - Allow verbose sub-agent returns. Production showed this fails at scale — two 18min+ stalls.
Source files
skills/implement/SKILL.md · documents commands/pipe.md · references pipeforge-mcp/src/tools/chunk-results.ts · implements
Summary
The codebase map is a navigation aid for planning sessions, not a feature documentation system.
Content must pass the "any feature" test: would a developer working on a completely different
module need this? Excludes specific values, bug-fix details, task-specific gotchas, and feature
internals. Bias toward advancing cursor without content changes on incremental updates.
Why it exists
The codebase map was drifting toward feature-level documentation. An incremental update on a
real project (28 commits stale) ran for 12 minutes, consumed 96k tokens, and saved details like
buffer size changes and the reasons behind them. This is the same trajectory that killed module
maps — too detailed, too expensive, and ultimately ignored by the agents consuming them.
The root cause was twofold: (1) the code-explorer agent is designed for deep
feature analysis and its default output guidance overrode the synthesis skill's lighter prompts;
(2) the incremental path's behavioural-change filter was too loose — "update if they reveal
new patterns" let almost anything through.
How it works
The "any feature" test. Applied at every content decision point: would a
developer working on a COMPLETELY DIFFERENT module need to know this? If no, it doesn't belong
in the map.
Explicit exclusion list:
- Specific values and sizes (buffer sizes, timeouts, thresholds, config constants)
- Individual bug fixes and their root causes
- Task-specific gotchas that only matter when touching one feature
- Feature implementation details (algorithms, encoding, dedup logic, sort routing)
- Internal ticket references as gotcha anchors (entries anchored to a single ticket are a smell)
- Changelog-style entries ("Added in <ticket-id>")
Incremental bias: default is "advance cursor only" — content updates are the
exception, reserved for genuinely structural changes (new modules, new shared base classes,
new cross-cutting conventions).
Code-explorer guardrails. When launched for synthesis, code-explorer agents
receive an "Abstraction Level — MAP, NOT IMPLEMENTATION" block that overrides their default
deep-dive guidance with good/bad examples.
Rationale
The codebase map's value is proportional to its signal-to-noise ratio, not its completeness.
A 200-line map of module boundaries and cross-cutting patterns loads in seconds and informs
every planning session. A 400-line map with buffer sizes, encoding details, and per-task gotchas
costs more to maintain (12min/96k tokens for one incremental update) and creates noise that
obscures the structural knowledge. The "any feature" test naturally filters to the right
abstraction level — project-wide architecture survives, feature internals don't.
Alternatives considered
- Reduce the map size limit (e.g., 100 lines). The problem is content quality, not quantity. A 100-line map with buffer sizes is still wrong.
- Use a different agent type for synthesis. Code-explorer is the right tool for codebase scanning — it just needs different output instructions for map-level vs task-level work.
- Split the map into sections with different update frequencies. Overcomplicated. The simpler fix is tighter content rules applied uniformly.
Source files
plugin/skills/synthesise-project-knowledge/SKILL.md · implements plugin/commands/deliver.md · implements plugin/agents/code-explorer.md · references
Relationships
codebase-map-cursor · depends-on module-maps-deprecated · depends-on post-merge-agents · depends-on context-budget-management · part-of
Summary
How task-specific knowledge (discoveries, plan, findings) flows from planning through sealing
to implementation via MCP persistence. Discoveries are saved during planning, the plan is sealed
to MCP, and both are loaded by implement, heavy-review, and e2e-gen phases — enabling context
survival across /clear boundaries and auto-compaction.
Why it exists
Claude Code conversations are ephemeral — /clear wipes them, auto-compaction
summarises them lossily, and session boundaries end them. But implementation phases need the
technical context gathered during planning: which files to modify, which patterns to follow,
which gotchas to avoid.
MCP acts as the persistence bridge. Saving discoveries and plan to MCP before the
context boundary lets downstream phases load exactly what they need without re-exploring.
How it works
Planning (/breakdown):
- Code-explorer sub-agents explore the codebase → findings returned to main context.
save-code-discoveries persists findings to MCP via saveDiscoveries(taskId, content, parentTaskId?). - Parent discoveries auto-resolve:
getDiscoveries(taskId) returns both task and parent content. - Discoveries are purely technical — decisions stay in conversation for
task-enrich to mine.
Sealing (/pipe-seal):
save-plan (shell) transfers plan file → MCP at zero LLM cost. task-enrich mines conversation → pushes business context to tracker. - Pipeline state created on MCP with task ID, branch, pipeline type.
Implementation (/pipe):
getTaskContext(taskId) loads discoveries, findings, and chunk artifacts in one call. getPlan(taskId) or getPlanChunk loads the plan. - Implement sub-agents receive task ID only — they self-load context from MCP.
- Sub-agents do NOT load
getCodebaseMap() — the plan already distils map knowledge. - Heavy-review receives the branch diff plus MCP findings for classification.
Recovery:
- If
getPlan returns null at implement, /pipe attempts shell recovery via pf-local save-plan. - If the plan file is also missing → STOP with re-seal message.
- Missing discoveries → STOP with "Run
/breakdown first" (hard gate).
Key gotchas
- Discoveries are TECHNICAL only — decisions belong in the conversation and are mined by
task-enrich. - Sub-agents self-load context — orchestrator passes only the task ID.
- Discovery content should be ~100 lines max. File paths without WHY are useless after
/clear. - Plan transfer ALWAYS uses shell — never load plan content into LLM context via MCP tools directly.
Rationale
The discovery chain exists because the three-phase lifecycle creates deliberate context
boundaries (/clear) that would otherwise destroy all planning knowledge. MCP is
the structured persistence layer — each data type is saved at the natural point in the workflow
where it's produced, and loaded on-demand by consumers. The self-loading pattern prevents the
orchestrator from becoming a context bottleneck. Plan transfer always uses shell to keep plan
content out of LLM context.
Alternatives considered
- Pass discovery content in orchestrator prompts. Loads all discovery content into the orchestrator's context window just to relay it.
- Have agents call
savePlan MCP tool directly for recovery. Loads plan content into agent/orchestrator context. Shell handles transfer at zero LLM cost. - Summarise planning context instead of persisting verbatim. Summarisation is lossy — specific file paths, pattern details, and gotchas get simplified away.
Source files
skills/save-code-discoveries/SKILL.md · implements skills/implement/SKILL.md · implements commands/pipe.md · implements bin/commands/save-plan.mjs · implements
Relationships