Open
Conversation
…t guidance
Introduces the full SpecKit-driven design artefact set for multi-repo
workspace orchestration (feature 001-workspace-worktree-orchestration).
Captures three clarification sessions (2026-04-13, 04-14, 04-17), the
24-FR spec, the execution plan with Constitution Check, the 24-task
breakdown, and four quality checklists.
Adds repository-level governance so agents and reviewers can act from
a stable context:
- CLAUDE.md hierarchy for per-directory responsibility boundaries
- AGENTS.md with current runtime truth + resolved design decisions
- .specify/ SpecKit templates, scripts, stable memory
- .claude/ commands and agent definitions
- .agents/ skills including workspace-worktree-context
- .gitignore entries for local state
Feature artefacts under specs/001-workspace-worktree-orchestration/:
- spec.md (24 FRs, 3 user stories)
- plan.md (Constitution PASS, risk register, FR-023 ordering)
- tasks.md (T001..T024 ordered by phase, MVP-complete)
- checklists/{requirements,state,contract,error}.md (iteration-2)
- checklists/archive/ (iteration-1 kept for audit trail)
- context/{current-state,target-state,go-live-runbook,pr-description}.md
- cases/{reconcile-cases,repo-path-resolution,workspace-metadata-authority}.md
Implementation follows in the next commit.
Refs: Constitution v1.0.0 Principle II (spec-driven change control);
spec FR-001..FR-024.
…mmand
Ships the workspace orchestration feature designed in the preceding
spec commit. Users can now run /dev <name> to create or reconcile a
mirrored multi-repo workspace under <cwd>/../worktrees/<name>/ with
one forked OpenCode session — fully headless.
New modules under src/plugin/worktree/:
dev-command.ts FR-024: ensureDevCommand idempotently creates
.opencode/commands/dev.md; OpenCode's native
command scan picks it up so /dev <name> works
alongside the worktree_workspace_create tool.
workspace.ts FR-002 name validation (regex); FR-003 auto-detect
git repos in direct subdirs; FR-004 target path
resolution with nesting rejection; FR-016 warnings
for unrelated root-level content; FR-017 per-path
mutex; T008 namespace conflict check against
OpenCode built-in commands (init, review) and
existing .opencode/commands/<name>.md files.
workspace-create.ts FR-009 dual-path pre-check (confirmed collision
= whole reject; per-repo pre-check failure =
per-repo status=failed); FR-005 branch naming
(local short name preserved as-is; SHA[:12] for
detached HEAD); FR-007 orphan handling;
FR-008 ghost prune + retry; FR-022 health check;
FR-023 strict mutation ordering with partial-
rollback semantics; FR-019 response schema.
workspace-session.ts FR-013/FR-014 single workspace session
fork/reuse/refork; FR-018 always headless.
config.ts Shared .opencode/worktree.jsonc schema + loader
(extracted from worktree.ts for reuse).
git.ts Shell-safe git wrapper, branch name validation,
Result<T,E> type (extracted from worktree.ts).
sync.ts File copy, symlink, hook execution with timeout
(extracted from worktree.ts).
Modified:
worktree.ts Wires ensureDevCommand into plugin entry
alongside loadWorktreeConfig; adds
worktree_workspace_create AI tool; preserves
legacy worktree_create/worktree_delete
behaviour unchanged (FR-020).
state.ts Extends SQLite schema with workspace_associations
and workspace_members tables per FR-021
(per-project DB only; no global registry).
README.md Documents /dev <name> usage and the
worktree_workspace_create tool.
Spec drift fixes applied in this commit (found via literal-Grep audit
against the 2026-04-17 spec):
- FR-005: removed unauthorised iteration-2 "/"→"-" substitution rule
(baseBranch.replace(/\//g, "-") deleted); now preserves slashes
verbatim in branch refs.
- FR-005: detached HEAD SHA length 8 → 12 (--short=8 → --short=12)
for collision resistance.
- FR-009: checkBranchCollisions refactored from
Result<void, CollisionError[]> to PreCheckOutcome with separate
collisions and preCheckFailures arrays; caller now handles both
paths distinctly.
- T008/H8: added checkWorkspaceNameAvailable for namespace conflict
detection before any worktree mutation or FR-024 auto-create.
- CollisionError: fixed pre-existing bug where push used the field
name "name" but the interface declared "repoName"; no behavioural
change for callers, fixes type consistency.
- planRepoWorktrees JSDoc: removed stale mention of "/"→"-" rule and
"abbreviated SHA" (found by pr-review-toolkit:code-reviewer in
post-implementation review).
Static validation: bun build --target=bun --external '*' succeeds on
worktree.ts, workspace.ts, workspace-create.ts with zero errors
(30ms). Full type/link check runs upstream in OCX monorepo.
Smoke tests: see
specs/001-workspace-worktree-orchestration/context/go-live-runbook.md
for S1..S10 scenarios covering FR-005/009/019/022/023/024 plus
namespace conflict and partial-success paths.
Refs: spec FR-001..FR-024; plan.md Risk Register; tasks T001..T024
(all previously marked [需复审] items moved to [已对齐 2026-04-19]).
Addresses 11 of 16 unresolved review threads from PR #1 round 1: src/plugin/worktree/state.ts (thread 13, coderabbit): - SIGINT/SIGTERM cleanup now re-raises the signal after pool close, so the process actually terminates; without this Ctrl+C could hang if anything else kept the event loop alive. src/plugin/worktree/workspace-create.ts (thread 15, coderabbit): - existingMembers loader now fails fast on per-project DB init error instead of swallowing it; the previous behaviour could misclassify a healthy worktree as an orphan and `rm -rf` it, destroying uncommitted work. Stale documentation synced with 2026-04-17 spec (threads 4, 5, 6, 10, 11, 12): - .specify/memory/active-feature-context.md: branch naming (no /→-, SHA[:12]), FR-009 dual-path, FR-024 slash mechanism, FR-019 error MUST. - .specify/memory/project-memory.md: Current Shipped Behavior now includes worktree_workspace_create + /dev slash + workspace tables. - .specify/memory/constitution.md: Repository Constraints reflect multi-repo workspace runtime. - specs/.../cases/reconcile-cases.md: Branch Name Computation — no substitution, SHA[:12]. - specs/.../plan.md: Module Contracts — PreCheckOutcome + dual-path checkBranchCollisions signature. - specs/.../context/go-live-runbook.md: Smoke setup now forces `git init -b main` + inline identity so repoA/repoB deterministically land on main without depending on global git config. .specify/scripts/powershell/ (threads 3, 7, 8): - check-prerequisites.ps1: branch validation moved after -PathsOnly early return (path-only mode must skip prerequisite validation). - setup-plan.ps1: human-readable status routed through Write-Verbose when -Json is set, so JSON stdout stays parseable. - install-user-speckit.ps1: prefer $HOME (cross-platform) and fall back to $env:USERPROFILE; throw if both are missing. Deferred (will reply in PR comments): - sync.ts bash -c on Windows (thread 0) — platform-dependent hook execution, deferred to platform-abstraction follow-up. - workspace-create.ts error-string matching (thread 1) — common git pattern; risk of localisation accepted. - workspace.ts RESERVED_COMMAND_NAMES hardcoded (thread 2) — documented as known limitation L2; future dynamic query deferred. - update-agent-context.ps1 -replace escape (thread 9) — replacement of [Environment]::NewLine works as expected; no fix needed. - sync.ts Bun.spawnSync discussion (thread 14) — reviewed; current async handling is correct in context.
…spawn Addresses thread 5 (coderabbitai, sync.ts:193) from PR #1 round-1 review. Bun.spawnSync blocks the entire Bun event loop until the child process exits; for long-running hooks like `pnpm install` (typical 20-60s), this stalls every other async operation in the plugin — including the FR-023 parallel-phase hooks running for sibling repos, session-event delivery, and incoming SDK calls. Switch to async Bun.spawn + `await proc.exited`. Behaviour is preserved (same error classification, same stderr capture, same withTimeout wrapping) but the event loop stays responsive during hook execution, which is essential for the workspace-level parallel pipeline. Static verification: `bun build --target=bun --external '*'` on sync.ts bundles cleanly (17ms, 3.68 KB).
Critical fixes:
- common.ps1 Test-FeatureBranch: switch to Write-Error to avoid
pipeline pollution that silently swallowed branch validation
failures in `if (-not (Test-FeatureBranch ...))` callers.
Major fixes:
- workspace-create.ts planRepoWorktrees: introduce PlanningOutcome
returning { plans, planningFailures }. Stop fabricating
`dev_unknown_*` branches when `git rev-parse HEAD` fails; emit a
per-repo planning failure that flows into MemberOutcome as
`status="failed"`, matching the FR-009 dual-path pattern.
- workspace-create.ts orchestrator: scan every member repo's DB for
a stored workspace session, not just repos[0]. Preserves FR-013's
"exactly one workspace-level session" invariant when a newly-added
repo appears first in detection order.
- state.ts workspaceAssociationSchema: tighten sessionDisposition
from z.string() to z.enum(["forked","reused"]) to match the
SessionDisposition TypeScript union.
Minor fixes:
- install-user-speckit.ps1: use -LiteralPath on New-Item to handle
skill-name chars PowerShell treats as wildcards; skip source files
with empty body instead of writing an unusable frontmatter-only
SKILL.md.
- plan.md: add `text` language labels to the three integration-flow
diagram fences for markdownlint MD040 compliance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CodeRabbit identified that worktreeListDetailed silently returned [] on `git worktree list` failure, making the try/catch in checkBranchCollisions dead code. The pre-check would then pass as "collision-free" despite never having queried git — violating FR-009 case (b), which requires marking the repo status="failed" and excluding it from subsequent mutation. Changes: - git.ts: worktreeListDetailed now returns Result<WorktreeEntry[], string>. Empty array on parse success, Result.err(gitError) on git failure. - workspace-create.ts checkBranchCollisions: replace try/catch with `if (!result.ok)` branch; cache map now stores WorktreeEntry[] directly. - workspace-create.ts isWorktreeHealthy: treat git failure as unhealthy so reconcile re-runs `git worktree add` (which will surface the underlying git error through normal error paths). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CRITICAL fix: - workspace-create.ts planRepoWorktrees: refuse to delete arbitrary directories during "orphan recovery". The previous pattern marked ANY existing <workspace>/<repo> directory as deletable, which could wipe user files or a retained worktree with uncommitted changes. Now: only adopt the path if it has a .git entry AND appears in `git worktree list` for the repo (reuse the existing branch in that case). Anything else surfaces as a planning failure — never a destructive mutation. - workspace-create.ts executeSingleRepo: drop the now-unreachable `rm -rf worktreePath` orphan-removal step and the `rm` import, so future changes cannot accidentally reintroduce the dangerous path. Major fixes: - workspace-create.ts orchestrator: split outcomes into `persistableOutcomes` (execution + pre-check failures, all with non-empty branch names) and response-only `planningFailureOutcomes` (branch = ""). Pass only `persistableOutcomes` to `persistWorkspaceState`. Previously, a single HEAD-unresolvable repo would make `workspaceMemberSchema` (branch.min(1)) throw inside persistence, converting a per-repo failure into a whole-command failure that left successful repos unrecorded. - workspace-create.ts persistWorkspaceState: wrap per-repo `upsertWorkspaceAssociation` + `upsertWorkspaceMember` in a `db.transaction()`. A partial persistence was leaving a session binding without a matching member row, which the next reconcile classified as an orphan and could (pre-Critical fix) trigger destructive recovery. Transaction ensures both succeed or both rollback. - common.ps1 Test-FileExists / Test-DirHasFiles: replace `Write-Output` status prints with `Write-Host` so callers' boolean predicates (`if (-not (Test-FileExists ...))`) see the `$true/$false` return value instead of a truthy string array. Same failure mode already guarded in Test-FeatureBranch (round-3). Minor fixes: - common.ps1 Get-CurrentBranch + Resolve-Template: use -LiteralPath on all Test-Path / Get-ChildItem / Get-Content calls so repo paths with wildcard characters (`[`, `*`) resolve correctly instead of being interpreted as wildcard patterns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Major: unstick retry when the worktree directory still exists but is unhealthy. With round-4's rm removed, `executeSingleRepo` called `createWorktree` directly against a pre-existing stale directory, hitting `'<path>' already exists` which does not match the prune-retry heuristics (`already locked` / `already checked out`). Every reconcile then re-failed for the same reason, and the adoption branch in planning never re-entered because that only fires when there is no member record. Fix restores the rm step in `executeSingleRepo` under a stricter contract: `isOrphan: true` is now set ONLY in the existing-member + unhealthy branch of `planRepoWorktrees`, where the persisted member record proves the path is ours. The critical round-4 no-DB-record branch still refuses to touch non-adoptable paths and emits a planning failure instead — so the rm fires only on paths we provably own. Minor: eliminate the dead `branch: failedPlan?.branch ?? ""` fallback. `PreCheckFailure` now carries `branch` and `worktreePath` directly, populated from the plan at push-time in `checkBranchCollisions`. A new `PlanningFailure` interface (without branch/worktreePath) covers the planning-phase case where no plan exists yet. The orchestrator drops the `plans.find(...)` lookup and its defensive empty-string fallback, so a future refactor that desynced plans and pre-check failures would surface as a compile error instead of silently injecting `branch: ""` into persistence and throwing inside `db.transaction`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CRITICAL: don't rm healthy worktrees during retry.
When a previous run succeeded at `git worktree add` but failed later at
sync or hooks, the stored member has `status="failed"` but the worktree
itself is intact — potentially holding user work-in-progress between
runs. Round-5's blanket `retry` path would rm that healthy worktree.
Planning now distinguishes three cases for existing members:
- healthy + status != failed → reuse
- healthy + status == failed → retry with isOrphan=false (new branch)
- unhealthy → retry with isOrphan = (stale dir exists)
executeSingleRepo gates the rm + `git worktree add` behind a runtime
`isWorktreeHealthy` re-check: if the worktree is already healthy at
execution time (defense-in-depth against planning/execution races), it
skips straight to sync + hooks without touching anything on disk.
Major: include reuse plans in pre-check.
The round-4 orphan-adoption path emits `action="reuse"` using
`existingEntry.branch` verbatim, which may be a branch checked out at an
EXTERNAL worktree (user's `main` in the source repo). Dropping reuse
plans from `plansToCheck` let those external collisions slip through,
bypassing FR-009 on the adoption path. Include all plans; the existing
inside-workspace filter handles self-matches.
Minor: adopted branch must match workspace naming scheme.
No-DB-record adoption was happy to accept ANY branch (`main`, a stale
branch from another workspace, a user's personal branch) and persist it
as this workspace's member branch. That silently broke FR-005's
`dev_*_{workspaceName}_{YYMMDD}` contract. Adoption now requires the
branch to match `^dev_.+_{workspaceName}_\d{6}$`; anything else surfaces
as a planning failure so the user can resolve the conflict manually.
Minor: use `existing.worktreePath` for stale-dir detection.
DB's `existing.worktreePath` is the authoritative "we own this" signal;
checking the recomputed `path.join(workspacePath, repo.name)` would
leak the old directory if the workspace was moved between runs. Stat
and rm both now target `existing.worktreePath`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ration feat(worktree): ship /dev multi-repo workspace orchestration (FR-001..FR-024)
…lishing - Expand .gitignore to production standard (node_modules, dist, env, IDE, OS) - Replace repository-ownership CLAUDE.md with project-specific coding guidelines - Change package name from @opencode-ai/plugin-worktree to opencode-worktree - Add exports field, author, homepage, repository pointing to fork - Move dependencies to top-level (jsonc-parser, zod are runtime deps) - Add opencode-plugin and multi-repo keywords for discoverability
… engines - Use object-form entry in tsup.config.ts for explicit output path - Remove metadata artifact from CLAUDE.md footer - Add bun>=1.0.0 to engines field (plugin uses bun:sqlite API) - Keep jsonc-parser and zod in dependencies (bundled but listed for safety)
refactor: fork publishing setup (package.json rename, .gitignore, CLAUDE.md)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.