Skip to content

fix(plugin): consistent plugin ownership, per-project pools, targeted check/update, and settings directory contract - #39

Merged
sanchitrk merged 8 commits into
mainfrom
hotfixes
Sep 25, 2026
Merged

sanchitrk merged 8 commits into
mainfrom
hotfixes

Conversation

@sanchitrk

@sanchitrk sanchitrk commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes how plugins and settings resolve across the user and project layers, adds plugin check/update [spec], and enforces the host-directory contract. Five commits, one concern each:

Commit Linear What
ecf9cb304 COD-71 The CLI and runtime agree on which settings file owns a plugin entry (the last declaration in each view, D6). A { package } config entry no longer moves the registry a plugin loads from. The CLI reads both runtime views (boot and session) and acts once per store artifact.
d4dd03f95 COD-72 One plugin pool per view (the process view plus one per project root) instead of one per process. The same spec can run different builds in different projects without evicting each other. Follows opencode's per-location plugin supervisor.
7ae20700e COD-63 codework plugin check [spec] / update [spec]: match by source identity (package name in any version spelling, full git spec, path, or local package name). Unmatched specs exit 1 without probing anything.
1faf9724a n/a Each view resolves against its session's hostDir, never cwd; only the process view falls back to hostCwd. CLI plugin handlers are renamed to match.
90f278980 n/a --user-config-dir is now a hard override of the user settings file (read instead of <home>/settings.jsonc, below the project). --home and --user-config-dir resolve against hostCwd.

The directory contract (hostCwd / hostDir / cwd, --home / --user-config-dir) is written up in the Linear doc Host directories and settings layers.

Behaviour changes to note

  • --user-config-dir no longer adds a layer above the project. It replaces the home's settings.jsonc, the home's file is not read, and the project still wins. plugin add --user-config-dir X without -g now writes the project file; -g writes X/settings.jsonc.
  • A spec declared in both layers under different .npmrc registries is now two artifacts. plugin install/check/update/list show and handle both.
  • plugin reload rebuilds every view and reports each distinct failure once.

Testing

End-to-end tests: each drives the real CLI (loopback npm registry, local git+file:// repo, no network), boots the real runtime with a fake LLM, and writes a path-normalised artifact under packages/codework/test/__snapshots__/:

  • plugin-owner.layers.json: two registries in one process; project vs elsewhere sessions alternate project / user / project / user
  • plugin-owner.shared.json, plugin-owner.patch.json: shared artifact, and a { package } entry that doesn't re-anchor (fails without the fix)
  • plugin-targeted.json: check/update [spec] transcript, with the registry request log proving unrelated plugins aren't probed
  • settings-layers.json: --user-config-dir override; server-shaped run with a decoy under hostDir

__snapshots__/** is excluded from the formatter so artifacts stay byte-exact.

  • vp check, vp run typecheck: clean
  • harness: 919 passed / 12 skipped; codework: 107 passed

Test fixes (also in this PR)

Commit Linear What
a47bc34c7 COD-73 plugin.external.test.ts: the exchange helper passed its cwd as both the session's hostDir and the app's hostCwd. It now takes hostDir, and the app runs beside a different project, so the nearest-project test proves discovery follows hostDir. (A local sandbox's cwd is a host path by nature, so the other tests were already correct.)
2754110ff COD-74 The live persists finalized thinking test no longer depends on whether the provider sends a reasoning summary. Every attempt asserts persisted ⇔ streamed, and it retries in a fresh session (up to 3) until thinking arrives. 5/5 consecutive runs pass.

🤖 Generated with Claude Code

sanchitrk and others added 8 commits September 25, 2026 12:36
- Add `Settings.modules`: within one view the last string declaration owns a
  reference (D6), and a `{ package }` patch never does, so configuring a
  user-layer plugin from a project no longer moves the registry it loads from
- Make the CLI read both views the runtime resolves (boot: user layers; session:
  every layer) and keep one entry per store artifact, so a spec both layers
  name under different registries is installed, checked and updated twice,
  and under one registry stays a single line
- Expose the store digest as `Plugin.identity` for that dedup
- Cover it with E2E tests that drive the CLI against loopback registries, boot
  the real runtime, and write path-normalised artifacts to `__snapshots__`;
  keep those out of the formatter so they stay byte-exact

Refs COD-71. The per-session build selection is tracked in COD-72.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Keep one pool per settings view: the process view boot loads, and one per
  project root, created from it on first use and moved only by that project's
  sessions, so the same spec owned by two files can run two builds in one
  process and sessions in different projects never evict each other
- Treat a loaded reference as moved when this view's owner names another
  artifact the store already holds, which is how a project picks up the build
  its own `.npmrc` resolves
- Register event types from the union of every view, one plugin per ID
- Rebuild every view on reload, reporting each distinct failure once
- Extend the two-registry E2E to alternate a project session with one outside
  any project in a single process; its artifact now records both builds

Fixes COD-72. Scoping plugin state per location follows opencode's
plugin supervisor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Accept an optional `spec` on `plugin check` and `plugin update`; without it
  both walk every configured plugin exactly as before
- Match by source identity as the `plugins` array reads: a package by name
  whatever version it was written with, a git spec in full, a path by the
  file it resolves to, and a local package also by its manifest name (read
  without importing, via `Plugin.localName`); a plugin ID matches nothing
- Keep every match, so a spec both layers declare under two registries acts
  on both artifacts; fail an unmatched spec with a pointer to `plugin list`
  before anything is probed
- Name a targeted local plugin instead of skipping it silently, with no
  summary line for a comparison that never happened
- Cover it with one E2E that drives registry, git (`git+file://`) and local
  targets through the real CLI, proves unrelated plugins are never probed,
  runs the result in the runtime, and writes `plugin-targeted.json`

Closes COD-63.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Give a project view the host directory its sessions' `hostDir` led to for
  anchoring and parsing, falling back to `hostCwd` only in the process view,
  which has no session; `filed` parses against the reference's own anchor
- Name the CLI plugin commands' host anchor `hostDir` (the linked session's,
  or the directory the command runs in) instead of `cwd`, which is reserved
  for a sandbox's working directory
- Link E2E sessions only through `hostDir`, leave their sandbox directory
  alone, and run the app from a neutral `hostCwd`; the artifacts are
  byte-identical, so settings reach a session through `hostDir` alone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ings file

- Read `<--user-config-dir>/settings.jsonc` instead of `<--home>/settings.jsonc`
  rather than as a third layer above the project: the settings layers are
  now user, then project, and the home's file is not opened when the flag
  is given; credentials, cache and data stay in `--home`
- Resolve `--home` and `--user-config-dir` against `hostCwd`, never a
  session's `hostDir`, through one `Settings.userConfigDir`; `Settings.load`
  takes it already absolute and `paths` loses its `from`
- Let the flag choose which user file `plugin add/remove -g` edits instead of
  forcing every edit into it; without `-g` the project file is edited
- Describe `--home` and `--user-config-dir` accordingly in the CLI help
- E2E: CLI list and add plus a server-shaped runtime run (app `hostCwd` apart
  from the session's `hostDir`, with a decoy where a `hostDir`-relative
  resolution would land) write `settings-layers.json`; artifacts collapse
  path-sized column padding so they reproduce on any machine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rename the `exchange` helper's `cwd` to `hostDir`: it is the host
  directory the session is linked to, which is where its project settings
  come from, not a working directory
- Run the app from `root` beside an outer project while the session is linked
  to a nested one, so the nearest-project test proves discovery follows the
  session's `hostDir` and never the app's `hostCwd`

A local sandbox's `cwd` is a host path by nature, so the other tests that
place a session's sandbox in a temp directory were already right.

Fixes COD-73.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s choice

- Assert on every attempt that thinking is persisted exactly when thinking
  streamed, which is the harness's contract whatever the provider sends
- Ask for a reasoning summary more firmly (`medium`, `detailed`, a harder
  prompt) and retry in a fresh session, up to three times, until a response
  carries thinking, so the persistence path is exercised rather than skipped
- Scope the durable-event check to the chosen session

Fixes COD-74.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sanchitrk
sanchitrk merged commit f73b87d into main Sep 25, 2026
@sanchitrk
sanchitrk deleted the hotfixes branch October 8, 2026 06:18
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