You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(team): writable integration cherry-picks raw commits — no merge commits, clean per-subtask messages (v0.8.5)
/team --writable used to integrate each subtask worktree with
`git merge --no-ff -m "mmt(<SLUG>): merge <label>"`, which produced noisy
merge commits whose message embedded the 32-char task SLUG (e.g.
`mmt(audit-the-design-consistency-of-): merge detail-ui-polish`). The user
wanted a clean linear history: only raw per-subtask commits + conflict
resolution folded in, with well-formed messages.
Integration stage (workflows/team.mjs) now:
- replays each subtask branch onto the integration branch with
`git cherry-pick "$B".."<branch>"` (range from the merge-base) instead of
`git merge --no-ff` — NO merge commits, one raw commit per subtask commit.
Using the RANGE (not the bare branch tip) means a multi-commit branch — a
native solver or a fix re-dispatch may leave several — is fully replayed,
not silently truncated to its tip.
- commits each worktree with a clean conventional message scoped to the
short SUBTASK LABEL: `<type>(<label>): <summary>` (feat/fix/refactor/…),
explicitly NOT the task SLUG and never a "merge ..." message.
- decides "empty" by the branch's commit count ahead of the merge-base
(`rev-list --count`), NOT worktree dirtiness — so a subtask the agent
already committed itself (clean tree, non-empty branch) is integrated,
not dropped.
- resolves conflicts in place and folds the fix into the SAME raw commit via
`GIT_EDITOR=true git cherry-pick --continue` (no separate merge/resolution
commit, no editor hang in the TTY-less sub-agent); handles empty-after-pick
duplicates with `cherry-pick --skip`; `--abort` cleanly backs out the whole
range for an unreconcilable conflict (→ unresolved).
Docs (commands/team.md + workflow comments + INTEGRATE_SCHEMA descriptions)
updated to match. Verified end-to-end on throwaway repos (multi-commit range,
empty branch, empty-after-pick, conflict-resolved → all land as raw commits,
ZERO merge commits) and by codex through the live relay (3 rounds: FAIL→FAIL→
PASS, fixing a real multi-commit-drop bug and a contradictory empty-gate it
caught). 108/108 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .claude-plugin/marketplace.json
+2-2Lines changed: 2 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@
10
10
{
11
11
"name": "multi-model-team",
12
12
"description": "Delegates token-heavy, self-contained tasks to local CLI backends chosen by task size and type, with credit-exhaustion fallback through the backend chain to native Claude and a glanceable statusline HUD. Routing and shipped backend behavior are configured in config/roster.json; adding a new backend kind requires invoker and health support in code. /team runs a decompose → dispatch → verify → fix → synthesize pipeline; an optional config-gated UserPromptSubmit hook can proactively nudge delegation.",
"description": "Delegates token-heavy, self-contained tasks to local CLI backends chosen by task size and type, with credit-exhaustion fallback through the backend chain to native Claude and a glanceable statusline HUD. Routing and shipped backend behavior are configured in config/roster.json; adding a new backend kind requires invoker and health support in code. /team runs a decompose → dispatch → verify → fix → synthesize pipeline; an optional config-gated UserPromptSubmit hook can proactively nudge delegation.",
Copy file name to clipboardExpand all lines: package.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"name": "multi-model-team",
3
-
"version": "0.8.4",
3
+
"version": "0.8.5",
4
4
"type": "module",
5
5
"private": true,
6
6
"description": "Delegates token-heavy, self-contained tasks to local CLI backends (agy/Gemini, codex) chosen by task size and type, with credit-exhaustion fallback to native Claude and a statusline HUD. Node ESM, zero-build, cross-platform (Windows/Linux/macOS).",
Copy file name to clipboardExpand all lines: workflows/team.mjs
+37-29Lines changed: 37 additions & 29 deletions
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,7 @@ export const meta = {
7
7
{title: 'Dispatch',detail: 'dependency-ordered waves: each subtask on its assigned backend (CLI relay or native)'},
8
8
{title: 'Verify',detail: 'score each result against its acceptance criterion'},
9
9
{title: 'Fix',detail: 'bounded re-dispatch of failed subtasks with verifier feedback'},
10
-
{title: 'Integrate',detail: 'writable mode only: merge each worktree into the integration branch, orchestrator resolves any conflicts'},
10
+
{title: 'Integrate',detail: 'writable mode only: cherry-pick each worktree commit onto the integration branch (no merge commits), orchestrator resolves any conflicts'},
11
11
{title: 'Synthesize',detail: 'merge verified results into one answer'},
12
12
],
13
13
}
@@ -28,10 +28,11 @@ export const meta = {
28
28
// Modes: read-only (DEFAULT) — CLI agents return text, the orchestrator applies edits to the CURRENT
29
29
// branch, no branch/worktree/PR. writable (A.writable) — each subtask gets its own git worktree+
30
30
// branch off HEAD, the agent writes real changes there (CLI full-auto via run.mjs --cwd --writable),
31
-
// and a deterministic Setup/Integrate pair of Bash sub-agents create the worktrees and merge them
32
-
// into one integration branch `mmt/team-<slug>` off HEAD; the orchestrator RESOLVES any merge
33
-
// conflicts itself and completes the merge, so the user gets one finished, conflict-free branch (the
34
-
// user's branch is untouched; no gh PR). git runs in sub-agents because the Workflow runtime has no
31
+
// and a deterministic Setup/Integrate pair of Bash sub-agents create the worktrees and cherry-pick
32
+
// each subtask commit onto one integration branch `mmt/team-<slug>` off HEAD (NO merge commits — one
33
+
// clean raw commit per subtask); the orchestrator RESOLVES any conflicts itself and folds the fix
34
+
// into that same raw commit, so the user gets one finished, conflict-free branch with a clean linear
35
+
// history (the user's branch is untouched; no gh PR). git runs in sub-agents because the runtime has no
35
36
// fs/git; the workflow only orchestrates them deterministically (slug/labels, no Date/random).
// edits directly to the CURRENT branch. No branch, no worktree, no PR (back-compat).
86
87
// writable (A.writable===true / TC.mode==='writable'): each subtask gets its OWN git worktree +
87
88
// branch off current HEAD; the agent (CLI full-auto, or native) writes real changes there; then
88
-
// a deterministic integration stage merges every agent branch into ONE integration branch
89
-
// `mmt/team-<slug>` off current HEAD, the orchestrator RESOLVING any conflicts so the branch is
90
-
// finished + conflict-free (no auto-merge onto their branch, no gh PR). The Workflow runtime has
89
+
// a deterministic integration stage cherry-picks every agent's commit onto ONE integration branch
90
+
// `mmt/team-<slug>` off current HEAD (no merge commits — one clean raw commit per subtask), the
91
+
// orchestrator RESOLVING any conflicts so the branch is finished + conflict-free (no auto-merge
92
+
// onto their branch, no gh PR). The Workflow runtime has
91
93
// no fs/git, so the worktree lifecycle is run by sub-agents (Bash tool); the workflow
92
94
// orchestrates them deterministically.
93
95
// Accept boolean true OR the strings "true"/"writable"/"1"/"yes" (args/roster values may arrive as
@@ -242,18 +244,18 @@ const SETUP_SCHEMA = {
242
244
required: ['ok'],
243
245
}
244
246
245
-
// Writable-mode integration: the merge-into-integration-branch report.
247
+
// Writable-mode integration: the cherry-pick-onto-integration-branch report.
246
248
constINTEGRATE_SCHEMA={
247
249
type: 'object',
248
250
additionalProperties: false,
249
251
properties: {
250
252
ok: {type: 'boolean'},
251
253
integration_branch: {type: 'string'},
252
254
base_sha: {type: 'string'},
253
-
merged: {type: 'array',items: {type: 'string'},description: 'labels that merged CLEANLY into the integration branch'},
254
-
resolved: {type: 'array',items: {type: 'string'},description: 'labels that hit conflicts which the orchestrator RESOLVED, then completed the merge'},
255
+
merged: {type: 'array',items: {type: 'string'},description: 'labels that cherry-picked CLEANLY onto the integration branch (one raw commit each)'},
256
+
resolved: {type: 'array',items: {type: 'string'},description: 'labels that hit conflicts which the orchestrator RESOLVED, folded into the same raw commit (cherry-pick --continue)'},
255
257
unresolved: {type: 'array',items: {type: 'string'},description: 'labels whose conflict could not be safely resolved and were left for the user (rare)'},
256
-
empty: {type: 'array',items: {type: 'string'},description: 'labels whose worktree had no changes to merge'},
258
+
empty: {type: 'array',items: {type: 'string'},description: 'labels whose worktree had no changes to integrate'},
257
259
summary: {type: 'string',description: 'human-readable summary of the integration outcome + how to inspect/merge the branch'},
258
260
reason: {type: 'string'},
259
261
},
@@ -646,37 +648,43 @@ const usage = {
646
648
}
647
649
if(cliRecords.length)log(`usage: ${cliRecords.length} subtask(s) executed on CLI (off-budget); ${nativeRecords.length} on native Claude`)
`You are the WRITABLE-MODE INTEGRATION agent for the multi-model-team /team pipeline. Use the Bash tool (plus the Read/Edit/Write tools when resolving a conflict) to merge each subtask's worktree into the integration branch AND to resolve any merge conflicts yourself. The goal is ONE finished, conflict-free integration branch the user can merge as-is — not a pile of worktrees for them to reconcile.
666
+
`You are the WRITABLE-MODE INTEGRATION agent for the multi-model-team /team pipeline. Use the Bash tool (plus the Read/Edit/Write tools when resolving a conflict) to bring each subtask's worktree changes onto the integration branch AND to resolve any conflicts yourself. The goal is ONE finished, conflict-free integration branch with a CLEAN, LINEAR history the user can merge as-is — NO merge commits, just one well-described raw commit per subtask.
663
667
664
-
CRITICAL SAFETY RULE: all merges + conflict edits happen INSIDE the dedicated integration worktree at "${INT_WORKTREE}" (checked out to "${INT_BRANCH}"). NEVER run \`git switch\`/\`git checkout\` in the main working tree, and NEVER merge the integration branch into the user's current branch. The user's checkout must end exactly as it started.
668
+
CRITICAL SAFETY RULE: all cherry-picks + conflict edits happen INSIDE the dedicated integration worktree at "${INT_WORKTREE}" (checked out to "${INT_BRANCH}"). NEVER run \`git switch\`/\`git checkout\` in the main working tree, and NEVER merge the integration branch into the user's current branch. The user's checkout must end exactly as it started.
665
669
666
670
Integration branch: "${INT_BRANCH}" (already created off the original HEAD). Integration worktree: "${INT_WORKTREE}".
667
671
Subtasks (each wrote changes into its own worktree):
668
672
${labelLines}
669
673
670
674
Do EXACTLY this, in order:
671
-
1. For EACH subtask worktree: \`git -C "<worktree>" add -A\`; if there are staged changes, commit them: \`git -C "<worktree>" commit -m "mmt(${SLUG}): <label>"\`. If the worktree has NO changes (\`git -C "<worktree>" status --porcelain\` empty), record the label under "empty" and skip it.
672
-
2. From the INTEGRATION WORKTREE only, merge each non-empty subtask branch into "${INT_BRANCH}", one at a time, no-fast-forward (visible merge commit):
- If it merges cleanly, record the label under "merged".
675
-
- If it CONFLICTS: do NOT abort. RESOLVE it. Inspect the conflicted files in "${INT_WORKTREE}" (\`git -C "${INT_WORKTREE}" diff\`, the \`<<<<<<<\`/\`=======\`/\`>>>>>>>\` markers), read BOTH sides, and edit each conflicted file to a correct COMBINED result that preserves the intent of every subtask (don't just pick one side unless that is genuinely correct; remove ALL conflict markers). Then \`git -C "${INT_WORKTREE}" add -A\` and \`git -C "${INT_WORKTREE}" commit --no-edit\` to complete the merge. Record the label under "resolved" (it IS merged — just needed manual conflict work). If a conflict is genuinely beyond safe resolution (e.g. fundamentally contradictory changes you cannot reconcile without guessing at intent), THEN \`git -C "${INT_WORKTREE}" merge --abort\`, record it under "unresolved", keep that subtask's worktree, and continue.
675
+
1. COMMIT each subtask's UNcommitted changes in its OWN worktree with a CLEAN, conventional message — do NOT decide "empty" here (an agent may have already committed its own work, leaving a clean tree but a non-empty branch; emptiness is decided in step 2 by the branch's commit count, never by worktree dirtiness). For EACH subtask worktree: \`git -C "<worktree>" add -A\`; then, ONLY if \`git -C "<worktree>" status --porcelain\` is NON-empty (there are staged changes the agent left uncommitted), commit them as ONE raw commit:
676
+
\`git -C "<worktree>" commit -m "<message>"\`
677
+
(If the tree is already clean, the agent committed its own work — that's fine; do NOT mark it empty and do NOT skip it, just move on; step 2 will integrate whatever commits the branch carries.)
678
+
The <message> MUST be a clean conventional-commit line scoped to the SUBTASK LABEL (NOT the long task slug): \`<type>(<label>): <imperative one-line summary>\` — e.g. \`feat(detail-ui-polish): tighten listing card spacing and hover states\`. Choose <type> from feat/fix/refactor/docs/test/chore by what the subtask actually did; derive the summary from the subtask's stated goal above; keep the subject ≤72 chars, no trailing period. Do NOT use the task slug "${SLUG}" as the scope and do NOT write "merge ..." messages.
679
+
2. From the INTEGRATION WORKTREE only, replay each subtask branch's commit(s) onto "${INT_BRANCH}" as RAW commits with NO merge commit, one branch at a time. FIRST decide emptiness by the BRANCH's commit count (NOT worktree dirtiness): compute the base \`B="$(git -C ${JSON.stringify(INT_WORKTREE)} merge-base ${JSON.stringify(INT_BRANCH)} "<branch>")"\` (the original HEAD), then \`N="$(git -C ${JSON.stringify(INT_WORKTREE)} rev-list --count "$B".."<branch>")"\`. If N is 0 the subtask produced no commits — record the label under "empty" and skip to the next branch. Otherwise the branch carries N≥1 commit(s) (usually 1, but a native solver or a fix re-dispatch may leave several — integrate ALL of them, drop none); cherry-pick the whole RANGE so nothing is lost:
680
+
\`GIT_EDITOR=true git -C ${JSON.stringify(INT_WORKTREE)} cherry-pick "$B".."<branch>"\` (the range picks ALL of that branch's commits in order, each as its own raw commit — never a merge commit. \`GIT_EDITOR=true\` so nothing opens an editor; you have no TTY.)
681
+
- If the whole range applies cleanly, record the label under "merged".
682
+
- If git reports "The previous cherry-pick is now empty" / "nothing to commit" for an individual commit (its changes were already applied by an earlier subtask), that commit is a redundant no-op: \`git -C "${INT_WORKTREE}" cherry-pick --skip\` and continue the range. (Do NOT count this as a conflict or as unresolved.)
683
+
- If it CONFLICTS: do NOT abort. RESOLVE it. Inspect the conflicted files in "${INT_WORKTREE}" (\`git -C "${INT_WORKTREE}" status\`, \`git -C "${INT_WORKTREE}" diff\`, the \`<<<<<<<\`/\`=======\`/\`>>>>>>>\` markers), read BOTH sides, and edit each conflicted file to a correct COMBINED result that preserves the intent of every subtask (don't just pick one side unless that is genuinely correct; remove ALL conflict markers). Then \`git -C "${INT_WORKTREE}" add -A\` and \`GIT_EDITOR=true git -C "${INT_WORKTREE}" cherry-pick --continue\` (keeps the existing message, never opens an editor) to land it as ONE raw commit with the resolution folded in — NO separate merge/resolution commit — then let the rest of the range continue. Record the label under "resolved". If a conflict is genuinely beyond safe resolution (fundamentally contradictory changes you cannot reconcile without guessing at intent), THEN \`git -C "${INT_WORKTREE}" cherry-pick --abort\` (this rewinds the WHOLE range for that branch back to its pre-pick state), record it under "unresolved", keep that subtask's worktree, and continue with the next subtask.
676
684
3. After integrating, sanity-check the integration worktree builds/parses if it's quick and obvious (e.g. \`node --check\` a changed .mjs); note any breakage in the summary. Then remove the cleanly-merged + resolved + empty subtask worktrees (\`git worktree remove --force "<path>"\`). Keep ONLY any "unresolved" worktrees AND the integration worktree "${INT_WORKTREE}". Do NOT delete the integration branch and do NOT touch the user's branch.
677
-
4. Report honestly.
685
+
4. Report honestly. The final \`git -C "${INT_WORKTREE}" log --oneline\` should show clean per-subtask commits and ZERO "merge" commits.
678
686
679
-
Return JSON matching the schema: { ok, integration_branch, base_sha, merged:[label], resolved:[label], unresolved:[label], empty:[label], summary, reason? }. "resolved" = merged after you fixed conflicts; "unresolved" = the rare conflict you left for the user. The summary MUST tell the user the integration branch is ready to merge (e.g. "git log ${INT_BRANCH}"), note any "resolved" conflicts you reconciled (so they can review your resolution), and flag any "unresolved" ones.`,
687
+
Return JSON matching the schema: { ok, integration_branch, base_sha, merged:[label], resolved:[label], unresolved:[label], empty:[label], summary, reason? }. "merged" = cherry-picked cleanly; "resolved" = cherry-picked after you fixed conflicts (folded into the same raw commit); "unresolved" = the rare conflict you left for the user. The summary MUST tell the user the integration branch is ready to merge (e.g. "git log ${INT_BRANCH}"), note any "resolved" conflicts you reconciled (so they can review your resolution), and flag any "unresolved" ones.`,
0 commit comments