Skip to content

feat: add current-branch isolation mode#210

Merged
benym merged 24 commits into
rpamis:masterfrom
1919chichi:feat/current-branch-isolation
Jul 20, 2026
Merged

feat: add current-branch isolation mode#210
benym merged 24 commits into
rpamis:masterfrom
1919chichi:feat/current-branch-isolation

Conversation

@1919chichi

@1919chichi 1919chichi commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Closes #190

Summary

This PR implements first-class current-branch isolation for the Classic Comet workflow.

The design is based on #190. The issue asks for a mode where the user has already selected the correct integration branch and Comet must preserve that invariant: do not create a new branch, do not create a worktree, do not switch branches, and warn before continuing if the current branch changes unexpectedly.

Design Intent

This is not implemented as a loose isolation: current flag alone. The design treats current-branch isolation as a branch-bound workflow mode:

  • The selected branch becomes part of Comet's durable workflow state.
  • Standard entry commands must not silently rebind the workflow to whatever branch happens to be checked out later.
  • Drift detection must run consistently from state checks, phase guards, and write guards.
  • Rebinding must be explicit and auditable, because changing the bound branch changes the user's workflow invariant.
  • Hotfix/tweak workflows should no longer rely on a hidden default; they should ask the user which workspace isolation mode to use.

Key Design Decisions

1. Persist the branch binding in .comet.yaml

Before this change, the current-change sidecar could record the active branch and state select could overwrite that local value. That made branch drift structurally hard to detect: after an accidental checkout, selecting the change again could make the wrong branch look correct.

This PR moves the binding into the committed Classic state as bound_branch:

  • .comet/current-change.json only tracks which change is selected.
  • openspec/changes/<name>/.comet.yaml stores bound_branch as the durable branch binding.
  • bound_branch is machine-owned and cannot be set directly with comet state set <name> bound_branch ....
  • bound_branch is optional for compatibility and does not bump classic_migration.

2. Bind once, then block accidental drift

When the user chooses isolation: current, Comet captures the live Git branch as the initial bound_branch. Re-running the normal select/check flow does not overwrite that value.

If the workflow is later resumed from another branch, Comet reports the change as blocked and asks for an explicit decision: switch back to the bound branch, or intentionally rebind.

Detached HEAD is handled deliberately:

  • Comet refuses to create a new current-branch binding while HEAD is detached.
  • A previously bound change running under detached HEAD is treated as drift, not as a safe fallback.

3. Add explicit audited rebinding

This PR adds comet state rebind <change-name> for the rare case where the user intentionally wants the current-branch workflow to move to a different branch.

The command:

  • requires an existing binding,
  • refuses detached HEAD,
  • updates bound_branch to the current Git branch,
  • records a rebind event in .comet/state-events.jsonl.

The CLI command performs the state mutation; the workflow skills remain responsible for asking the user before invoking it.

4. Use one branch-binding verdict everywhere

The branch-binding logic is centralized in domains/comet-classic/classic-branch-binding.ts so the same rules are used by:

  • comet state check,
  • current-change resolution,
  • phase guards,
  • hook/write guards.

That avoids each entry point inventing slightly different drift semantics.

5. Make hotfix/tweak workspace isolation explicit

The design also removes the hidden hotfix/tweak default of isolation: current.

Hotfix and tweak now start with isolation: null and pause for an explicit user decision among:

  • continue directly on the current branch,
  • create/use a branch,
  • create/use a worktree.

This addresses the issue's current-branch use case without making current-branch work an invisible default. It also keeps worktree available for preset workflows.

6. Split archive branch handling by isolation mode

Archive behavior now matches the selected workspace model:

  • branch/worktree keep the existing feature-branch finishing flow.
  • current skips feature-branch merge/PR/keep choices and instead asks whether to push the current branch or keep it local.

This avoids presenting branch-management choices that do not fit a workflow intentionally running on the user's existing branch.

7. Surface the selected isolation in status

comet status now reports the selected isolation mode and bound branch in human output, and --json exposes the same data. This directly covers the visibility requirement from issue #190.

What Changed

  • Added bound_branch to Classic state serialization, validation, status projection, and runtime fixtures.
  • Added shared branch-binding verdict helpers and test coverage.
  • Slimmed current-change sidecar state so selecting a change no longer overwrites the bound branch.
  • Added drift detection to state checks, current-change resolution, phase guards, and hook guards.
  • Added comet state rebind <change-name> with audit-event recording.
  • Updated hotfix/tweak initialization and skills so workspace isolation is selected explicitly.
  • Updated archive/verify/build skill guidance for branch drift and current-branch archive handling.
  • Updated comet status text and JSON output for isolation/bound-branch visibility.
  • Synced Chinese and English skill content, README references, generated Classic runtime, manifest/version metadata, and the 0.4.0-beta.6 changelog.

Compatibility

  • Existing changes without bound_branch remain readable because the field is optional.
  • classic_migration remains unchanged.
  • Legacy isolation: current changes without a binding can be lazily bound on a real Git branch during state checks/guards.
  • Non-Git projects are not blocked from choosing workspace isolation only because Git branch binding is unavailable.
  • branch and worktree workflows keep their existing branch-management behavior, with the same bound-branch safety checks applied where appropriate.

Validation

  • pnpm format:check
  • pnpm lint
  • pnpm build
  • pnpm test

Summary by CodeRabbit

  • New Features

    • Added workspace branch binding for isolation: current | branch | worktree, including persisted bound_branch.
    • Added comet state rebind to explicitly refresh a binding when needed.
    • Updated comet status to surface bound-branch details in both text and JSON.
  • Bug Fixes

    • Improved safety by blocking progress when the bound branch doesn't match or when HEAD is detached.
    • Skill discovery no longer crashes on malformed YAML in unrelated local skills.
  • Documentation

    • Updated READMEs, .comet.yaml field references, and workflow guides to reflect the new isolation/binding behavior and prompts.

1919chichi and others added 20 commits July 18, 2026 19:34
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQUYcRLiDWw6MRDAj5WQSc
Add bound_branch to legacyProjection's runKeys allowlist in the
0.3.9 differential contract test, alongside the existing isolation
entry — both are active-runtime-only projection fields not part of
the frozen 0.3.9 bash contract, so the differential comparison
should exclude them the same way.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @1919chichi, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The release adds persisted Git branch binding for current, branch, and worktree isolation modes, drift detection across selection and guard flows, comet state rebind, bound-branch status output, updated workflow guidance, regression tests, and version 0.4.0-beta.6. Malformed Skill YAML no longer aborts Skill discovery.

Changes

Current-branch isolation

Layer / File(s) Summary
Branch binding and state commands
domains/comet-classic/*
Classic state persists bound_branch, evaluates Git binding, supports healing and explicit rebind, records rebind events, and clears bindings when isolation is reset.
Selection and guards
domains/comet-classic/classic-current-change.ts, classic-guard.ts, classic-hook-guard.ts
Selection, hook governance, and build/verify/archive guards block branch drift and reuse branch-binding verdicts.
Status and workflow guidance
app/commands/status.ts, assets/skills/**, README*
Status JSON/text includes bound-branch information, while workflow documentation requires explicit isolation choices and drift handling.
Runtime bundle
assets/skills/comet/scripts/comet-runtime.mjs
The bundled YAML and Classic runtime includes the branch-binding, guard, selection, and state-command changes.
Tests and release metadata
test/**, package.json, CHANGELOG.md, assets/manifest.json
Tests cover binding, drift, rebind, status, workflows, and malformed Skill YAML; release metadata is updated to beta.6.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • rpamis/comet#203 — Covers the current-branch isolation design further implemented here across Classic state, guards, status, and workflow guidance.

Suggested reviewers: benym

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title matches the main change: it introduces current-branch isolation mode and related branch-binding behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@1919chichi
1919chichi marked this pull request as ready for review July 18, 2026 15:31
@1919chichi
1919chichi marked this pull request as draft July 18, 2026 15:31
1919chichi and others added 3 commits July 19, 2026 10:05
Centralize binding resolution in resolveBranchBinding (typed reads, lazy
git spawns, optional heal) and fix the drift-protection gaps found in
review:

- guard --apply re-reads the run context before the transition write so
  it no longer clobbers a bound_branch healed during the same run
- the PreToolUse hook enforces the binding for a sole active change even
  when no current-change selection file exists
- selections record the branch they were made on again, so pre-binding
  (isolation null) changes flag branch switches as stale like master did
- resolving the current change is read-only: the hook never heals (and
  can no longer throw on a failed heal write); heal happens on
  select/check/guard, which announce the lazily bound branch
- select refuses a drifted change instead of writing a selection that
  the next command immediately reports as stale, and no longer prints an
  empty "(branch: )" suffix for unbound changes
- switching isolation between workspace modes re-points the binding to
  the current branch; repeating the same mode keeps the sticky binding

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Document that the binding records the branch of the directory the
command runs in (run set/check/guard inside the worktree for worktree
mode), that switching workspace modes re-binds while repeating the same
mode keeps the binding, and that select refuses on drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge the within-branch compatibility fix into the Changed entries,
drop the sidecar implementation detail, and describe the final drift
semantics (single-change write guard coverage, select refusal, mode
switch rebinding) from the user's perspective.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@1919chichi
1919chichi marked this pull request as ready for review July 19, 2026 02:07

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @1919chichi, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
domains/comet-classic/classic-current-change.ts (1)

147-150: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle potential file read errors in resolveBranchBinding.

Both of these read-only paths invoke resolveBranchBinding without catching potential file system or parsing errors. If .comet.yaml is deleted or corrupted immediately after the initial validation (e.g., a TOCTOU race), the unhandled exception will crash the PreToolUse hook rather than cleanly blocking the operation using the established error-swallowing pattern.

  • domains/comet-classic/classic-current-change.ts#L147-L150: Wrap this call in a try/catch block and return { status: 'stale', reason: ... } on error.
  • domains/comet-classic/classic-hook-guard.ts#L324-L327: Wrap this call in a try/catch block and return { blockedResult: blockedStaleSelection(relativePath, ...) } on error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@domains/comet-classic/classic-current-change.ts` around lines 147 - 150, Wrap
the resolveBranchBinding call in
domains/comet-classic/classic-current-change.ts:147-150 with try/catch and
return { status: 'stale', reason: ... } on errors; likewise wrap the call in
domains/comet-classic/classic-hook-guard.ts:324-327 and return { blockedResult:
blockedStaleSelection(relativePath, ...) }. Preserve the existing
error-swallowing behavior and use each function’s established stale-selection
messaging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@domains/comet-classic/classic-current-change.ts`:
- Around line 147-150: Wrap the resolveBranchBinding call in
domains/comet-classic/classic-current-change.ts:147-150 with try/catch and
return { status: 'stale', reason: ... } on errors; likewise wrap the call in
domains/comet-classic/classic-hook-guard.ts:324-327 and return { blockedResult:
blockedStaleSelection(relativePath, ...) }. Preserve the existing
error-swallowing behavior and use each function’s established stale-selection
messaging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71b86af6-ad06-4297-acae-a8034d673527

📥 Commits

Reviewing files that changed from the base of the PR and between 5e97d19 and dc13773.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • CHANGELOG.md
  • README-zh.md
  • README.md
  • app/commands/status.ts
  • assets/manifest.json
  • assets/skills-zh/comet-archive/SKILL.md
  • assets/skills-zh/comet-build/SKILL.md
  • assets/skills-zh/comet-hotfix/SKILL.md
  • assets/skills-zh/comet-tweak/SKILL.md
  • assets/skills-zh/comet-verify/SKILL.md
  • assets/skills-zh/comet/SKILL.md
  • assets/skills-zh/comet/reference/comet-yaml-fields.md
  • assets/skills/comet-archive/SKILL.md
  • assets/skills/comet-build/SKILL.md
  • assets/skills/comet-hotfix/SKILL.md
  • assets/skills/comet-tweak/SKILL.md
  • assets/skills/comet-verify/SKILL.md
  • assets/skills/comet/SKILL.md
  • assets/skills/comet/reference/comet-yaml-fields.md
  • assets/skills/comet/scripts/comet-runtime.mjs
  • docs/superpowers/plans/2026-07-18-current-branch-isolation.md
  • docs/superpowers/specs/2026-07-18-current-branch-isolation-design.md
  • domains/comet-classic/classic-branch-binding.ts
  • domains/comet-classic/classic-current-change.ts
  • domains/comet-classic/classic-guard.ts
  • domains/comet-classic/classic-hook-guard.ts
  • domains/comet-classic/classic-state-command.ts
  • domains/comet-classic/classic-state-events.ts
  • domains/comet-classic/classic-state.ts
  • domains/comet-classic/classic-transitions.ts
  • domains/skill/find.ts
  • package.json
  • test/app/cli-help.test.ts
  • test/app/status.test.ts
  • test/domains/comet-classic/classic-branch-binding.test.ts
  • test/domains/comet-classic/classic-contract.test.ts
  • test/domains/comet-classic/classic-current-change.test.ts
  • test/domains/comet-classic/classic-guard.test.ts
  • test/domains/comet-classic/classic-hook-guard.test.ts
  • test/domains/comet-classic/classic-state.test.ts
  • test/domains/comet-classic/comet-scripts-guard.test.ts
  • test/domains/comet-classic/comet-scripts.test.ts
  • test/domains/skill/skills.test.ts
  • test/domains/skill/workflow-optimization-contract.test.ts

@benym

benym commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

classic-validate-command.ts中没有校验bound_branch只能是string或者null,虽然git分支命名上我们并不限制用户的名字,但是yaml校验通常数据类型都是string,比如叫123的分支名,会序列化为字符串 "123"
数字、数组或对象不应通过 comet validate,需要补充这种类型的校验和相关测试

完成之后,运行 pnpm build:classic-runtime 更新生成的 comet-runtime.mjs

- reject non-string, non-null bound_branch values in comet validate
- add regression tests for numeric, array, mapping, and quoted values
- rebuild comet-runtime.mjs bundle

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143C9RxpGHLN7f5asmmhfiC
@1919chichi

Copy link
Copy Markdown
Contributor Author

classic-validate-command.ts中没有校验bound_branch只能是string或者null,虽然git分支命名上我们并不限制用户的名字,但是yaml校验通常数据类型都是string,比如叫123的分支名,会序列化为字符串 "123" 数字、数组或对象不应通过 comet validate,需要补充这种类型的校验和相关测试

完成之后,运行 pnpm build:classic-runtime 更新生成的 comet-runtime.mjs

我在最新的提交中解决了这个问题,有空可以再review下

@benym

benym commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

LGTM

@benym
benym merged commit e97a0e6 into rpamis:master Jul 20, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support current-branch mode with no new branch or worktree

2 participants