Skip to content

Dev main 260422#5

Open
NeverMore93 wants to merge 20 commits intokdcokenny:mainfrom
NeverMore93:dev_main_260422
Open

Dev main 260422#5
NeverMore93 wants to merge 20 commits intokdcokenny:mainfrom
NeverMore93:dev_main_260422

Conversation

@NeverMore93
Copy link
Copy Markdown

No description provided.

wentaoyuan and others added 20 commits April 19, 2026 21:13
…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)
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.

1 participant