Skip to content

Collision guard: stacked PRs are not collisions (DL-025) - #53

Draft
jimutt wants to merge 5 commits into
v1from
fix/collision-guard-stacked-prs
Draft

Collision guard: stacked PRs are not collisions (DL-025)#53
jimutt wants to merge 5 commits into
v1from
fix/collision-guard-stacked-prs

Conversation

@jimutt

@jimutt jimutt commented Aug 31, 2026

Copy link
Copy Markdown
Owner

What

list-taken-ids.sh now skips open PRs whose head branch is an ancestor of HEAD, not just the PR whose head equals the current branch.

Why

Stacking is the normal DLD workflow: decisions land as one PR, implementation branches are cut from it. Those branches contain the decisions PR's commits, so every decision they exist to implement was reported as a collision and the run refused to start — with /dld-reindex prescribed, which would have had nothing to rename. Hit in a real project: origin/main had no decision records at all, yet DL-001..008 were all flagged.

An unfetched head ref stays in the taken set, so a missing fetch can't silently disable the guard.

Also

scripts/install-opencode-plugin.sh — symlink-based OpenCode plugin install, replacing the throwaway copy-and-patch script that silently misresolved packageRoot() to the project root. Verifies resolution before reporting success.

Tests

4 new bats tests (stacked exclusion, unrelated inclusion, unfetched fallback, own-branch exclusion), verified to fail without the fix. 309 bats, 140 bun, tsc, tessl green.

@jimutt
jimutt force-pushed the fix/collision-guard-stacked-prs branch from bd33912 to 08d1815 Compare August 31, 2026 13:04
jimutt and others added 5 commits August 31, 2026 18:06
…lision

DL-025. list-taken-ids.sh excluded only the PR whose head equals the
current branch, so a branch stacked on the decisions PR — the normal
DLD workflow — saw every decision it existed to implement reported as
colliding with the base, with /dld-reindex prescribed as the fix even
though nothing needed renaming.

find-collisions.sh now requests the taken set with --exclude-contained,
which also drops open PRs whose head is an ancestor of HEAD. The flag
is opt-in and the filter sits at the call site: those IDs are still
taken for allocation, since they exist in this branch's tree, and
plan-renames.sh depends on the unfiltered answer when choosing free IDs
for renamed records. An unfetched head ref stays in the set, so a
missing fetch can't silently drop a real claim.

Also adds scripts/install-opencode-plugin.sh, which symlinks the
OpenCode plugins into a project instead of copying them and patching
packageRoot() by regex. The regex was written for an older shape of
that function, silently stopped matching, and left the copy resolving
its scripts to the project root. The installer now verifies resolution
before reporting success.

6 bats tests: default-keeps-stacked-IDs, --exclude-contained drops
them, find-collisions ignores a stacked PR, unrelated-PR inclusion,
unfetched-head fallback, own-branch exclusion.
The TUI surfaces never loaded. OpenCode discovers loose .ts files and
immediate plugin package directories under .opencode/plugins/, and it
loads a CLI plugin only when a package exposes it through a ./tui
export. The installer had been dropping tui.tsx into a plugins/tui/
subdirectory, which is not a discovery location, so only the server
half ever ran — and an earlier attempt to have one file serve both
roles produced 'has invalid tui export', since v1 plugin modules are
target-exclusive.

extensions/opencode-dld-run/ gains a package.json exporting '.' as the
server module and './tui' as the CLI module, and the installer links
the directory as one package instead of placing the two files
separately. Verification now also asserts both default exports match
the { id, setup } contract, so a wrong-shaped module fails the install
rather than silently loading half the plugin.

Co-Authored-By: Kimi K3 <noreply@pi.dev>
Generated-By: pi 0.84.2
A symlinked plugin *directory* is skipped by OpenCode's discovery — a
symlink is not a directory to readdir — so the previous layout
registered nothing at all, silently: no load line, no error. Symlinked
loose files are picked up, which is why the earlier single-file layout
worked and hid the rule.

The installer now writes a real package directory whose three files are
one-line re-exports of the modules in dld-kit, so the code still has a
single home and edits take effect on reload. It also registers
./plugins/dld-run explicitly in .opencode/opencode.json, merging into an
existing plugins array when jq is available, since an explicit entry is
what the docs guarantee for loading a package's TUI component.

Verified against a live standalone session: the server log now shows
loading plugin id=.../.opencode/plugins/dld-run with no errors.

Co-Authored-By: Kimi K3 <noreply@pi.dev>
Generated-By: pi 0.84.2
The previous installer linked .opencode/plugins/dld-run straight at
dld-kit's extension directory. When the new package-directory installer
ran over that layout, mkdir -p was a no-op on the link and the
subsequent rm -f and writes resolved through it, deleting dld-kit's own
server.ts, tui.tsx and package.json and replacing them with shims that
re-exported themselves — hence 'export default cannot be used with
export *'.

The link at that path is now removed before the real directory is
created, and the installer refuses outright to write to any path
resolving inside the dld-kit checkout.

Regression-tested against a repo pre-seeded with the old symlink
layout: the install converts it to a real package directory and
dld-kit's sources are left untouched.

Co-Authored-By: Kimi K3 <noreply@pi.dev>
Generated-By: pi 0.84.2
package.json main/exports is not what the directory loader reads. With
the server entrypoint named server.ts, OpenCode logged 'configured
plugin directory has no index entrypoint' at WARN and dropped the
plugin from the location's plugin set — while still printing a
'loading plugin' INFO line for it, which made the failure look like a
success.

Renaming the shim to index.ts registers the plugin with both features:

  dld-run | features={"server":true,"tui":true} | status=active

Verified against the running service via GET /api/plugin.

Co-Authored-By: Kimi K3 <noreply@pi.dev>
Generated-By: pi 0.84.2
Co-Authored-By: Kimi K3 <noreply@pi.dev>
Generated-By: pi 0.84.2
Co-Authored-By: Kimi K3 <noreply@pi.dev>
Generated-By: pi 0.84.2
@jimutt
jimutt force-pushed the fix/collision-guard-stacked-prs branch from 08d1815 to 38f0271 Compare August 31, 2026 16:11
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