Repository navigation
feat: plugin manager - #35
Merged
Merged
Conversation
…dering
Plugin entries gain two long-form spellings alongside the module string:
`{ package }` resolves by location and installs, `{ plugin }` configures an
already-loaded plugin by ID and never installs. Exactly one key per entry;
an entry matching nothing is ignored rather than fetched.
Setup order is now declared instead of positional. A plugin states
`kind: "tool" | "prompt"` and the harness runs domains in table order, so a
prompt plugin always sees the full tool set regardless of which settings
layer contributed each entry. Within a domain, written order is kept and a
later entry with the same ID wins.
Settings discovery reads `.jsonc` (comments, trailing commas) with `.json`
as a fallback, and the project layer searches the startup directory and its
ancestors, nearest wins. Plugin entries accumulate across layers with
`{ "plugin": "<id>", "enabled": false }` as the per-project escape hatch.
A file that exists but cannot be read, parsed, or decoded now fails the
boot instead of being skipped.
`plugin add` validates by installing and importing before it writes;
`plugin remove` drops the module entry and any configuration written
against it. Both edit through jsonc-parser, preserving comments and
formatting, and write atomically. Default target is the project file,
`-g`/`--global` the user-wide one.
Also: `protocol` is no longer reserved — it is a typed catalog override, so
a local OpenAI-compatible endpoint can be pointed at correctly.
`Harness.layer` takes `cwd`, which makes tests hermetic now that discovery
walks up. Lint and typecheck cover `test/` in every package.
…ot the process's - Rename the discovery root to `hostDir` and keep `hostCwd` for the process's own directory, so the two meanings of `cwd` stop sharing a name; `run --cwd` still means the session directory and is untouched - Store `host_dir` on the session, nullable with no default, and carry it through `Session.create`, `fork` and a new `Session.link` that assigns or clears it on a session that already exists - Make `Settings.Service.load` take the root as an argument rather than binding one per layer, since one process serves sessions in different projects, or in none - Have `State.snapshot` read the session's root each exchange, so `link` applies at the next one; absent reads the user layer alone, with no fallback to the process's directory that would select a stranger's project and look correct - Accept an optional `hostDir` on `session.create` over RPC and report it on `SessionInfo`; the CLI passes its shell's directory and gains no flag - Find a project the way git finds a repository: the nearest ancestor holding a `.codework` directory, so an empty marker claims the answer and an inner project never merges with an outer one - Delete `codework.json*` outright — one layout, one place a relative plugin entry can anchor to - Create `.codework/` and print the path when `plugin add` runs where no ancestor is a project, because that decides where every later entry lands and a mistyped `cd` is how it goes wrong
… spawning pnpm - Parse every source form in one place: `Target` covers local paths, registry specs and git remotes, replacing the two half-parsers that each read a slice of the same grammar - Own the three rules parsing decides: whether re-resolving could yield different bytes, the canonical spelling two specs must share to be one install, and which sources are refused outright - Install with `@npmcli/arborist` in process, so nothing has to be on PATH first, `ignoreScripts` is enforced rather than requested, and the installed identity comes back as data - Resolve staleness with `pacote`, bypassing its metadata cache, because a staleness check that reads a cache is not one - Recover a git install's commit from the lockfile arborist writes, and fail when it cannot be found; falling back to the manifest version would report an update forever and never apply one - Opt in to git sources with `allowGit: "root"`: npm 12 refuses them by default, and `root` permits the source the user named while still refusing one a dependency drags in - Hand arborist the real path of the staging directory, or it returns a tree with no root edges and the install silently reports that it installed nothing - Name plugin failures once, as a tag for code and a lowercase `plugin-` reason for people and logs - Drop the pnpm spawn along with its process spawner and platform layers
…generations - Address an entry by the full digest of its canonical spec, under a readable slug, so two versions of one plugin sit side by side and a collision can never return the wrong package - Publish by renaming a staged directory into a numbered generation, leaving earlier ones intact for exchanges that already hold URLs into them - Keep the previous generation reachable, because the module registry is keyed by URL and never evicts, so installing over a path would hand back the old code on the next import - Validate a staged artifact before writing its marker, so a package that turns out not to be a plugin is never published and the generation already in use stays the answer - Carry what validation learned back to the caller instead of filing it: the store records packages, and knowing a plugin's id would mean encoding the result of running code it should ignore - Add `index.json` as an accelerator that disk always overrules, so deleting it costs a scan and nothing else - Move the install lock to one per store entry, so two plugins install at once and two installs of one plugin do not - Expire superseded generations at the end of an install that published something, keeping the two newest whatever their age - Drop the old flat cache layout along with `package.ts`
- Separate the two passes by cost: loading a module means a store lookup and an ESM import, while configuring one is pure data over what is already in memory - Produce a pool of loaded modules keyed by ID, carrying every string each one answers to, so a later entry can address a module by name, spec or path - Run the config pass at every exchange, so an options edit, an enabled flip or a reordering takes effect on the next turn with nothing re-imported - Hold the pool behind a mutable cell, read once at the top of a snapshot, so a future reload lands between exchanges and never inside one - Report an entry naming a module the pool does not hold, instead of reaching for the network from an exchange - Seed built-ins into the pool at construction, so disabling one goes through the same path as disabling anything else - Keep the combined helper as the composition of the two passes, for callers with no reason to hold a pool Project plugins now follow the session's host directory rather than the process's, which is only visible once both changes are in: a session that was never placed reads no project settings, so entries from a project file are loaded and then selected by nobody.
- Ask two questions at the top of every snapshot: is a configured entry filed but not loaded, and has a loaded module been superseded by a newer generation - Run a load pass only when one of them says yes, so the common case costs a lookup per filed module and nothing else - Record where each loaded module came from, since comparing generations is what makes an update finish: asking only whether an entry is loaded still answers yes after one - Require the resolve-only installer as a parameter, so an exchange cannot reach a registry by construction rather than by convention - Leave a module the store does not hold alone, and report it instead, so installing stays something a person asks for - Skip anything with no generation to compare, which is every built-in, supplied object and local path - Read the loaded set once, at the top, so an exchange already running is never disturbed
- Materialise what a checkout already declares with a no-argument install, which is what lets boot stay offline and name a remedy instead of reaching for the network - Print what each entry resolved to, using the declared id as proof the whole chain worked: an id cannot be known without importing, so its absence is where the reason goes - Recompute that reason every run rather than storing it, because a recorded failure goes stale the moment someone fixes the cause - Cache the opposite kind of fact, staleness from the network, in the store index where it survives a command exiting - Ask the network through a parameter, so the store keeps its own promise never to open a socket - Refresh only what is already installed, since not installed is not outdated and install is the verb for that - Re-anchor a local path to the settings file receiving it, and write it absolute when relative would name a different file from where it is read - Replace the loader's phase-tagged failure with the four reasons, and let an installer's failure through untouched, so one vocabulary reaches a person and a log line The old error shape was reporting a package that is not a plugin as an install problem, with a hint pointing at the registry. Two vocabularies for one failure was the cause.
- Cover the one case an exchange cannot notice: a local plugin edited in place, where nothing in settings changed and no generation moved because a local source is never filed - Append a counter to a local plugin's URL, since the module registry is keyed by URL and would otherwise hand back the module it already holds against an unchanged path - Keep the previous set when a reload fails, and report why, so a server does not empty its tool registry over a typo in a settings file - Read the process's own root when rebuilding, not one session's view, or the pool narrows to the caller's project and drops every other project's plugins - Expose it over RPC and as a command against a running server, with no error channel, because a failed reload is a result rather than a fault - Leave the CLI without a local form: every run is a fresh process, so the load pass already happened at startup
- Carry what the file says, what it resolves to, and which file it was, since flattening every layer's entries loses attribution that cannot be recovered: two layers may declare the same string - Derive the flat list from it, so the loader's input is unchanged and there is one source of truth - Print the written form in plugin list, because that is the string a person will search their settings for, rather than the anchored path it resolves to - Name the declaring file under --verbose and in the drift warning, which is the question worth answering once several layers accumulate entries
…n failure - Load resolve-only everywhere, so a start reports what is missing instead of fetching it: a boot that installs waits on the network, fails offline, and lets timing decide which code runs - Serialise the exchange-time load pass, and let a waiter adopt what the holder just produced, so two sessions reaching the boundary together do the work once - Carry the declaring settings file on every plugin failure, since several layers accumulate entries and which one to edit is the question - Attach it where the loader knows it, leaving an answer the store or installer already gave, because a more specific one is better
- Add session link, so a session created without a host directory can be given one later instead of being destroyed and recreated - Apply it at the next exchange, since settings are already re-read there: no reload and no restart, the project layer simply starts or stops being read - Identify the session by id and default the path to the shell's directory, because a project cannot identify a session and the CLI already runs on the host - Keep it apart from session relink, which moves where the work happens rather than where settings are discovered - Treat a local path that exists as bytes already here, so linking a project whose plugins are local loads them; without this the boundary check only ever acted on store entries - Leave a local source with no generation, so it can never be found superseded and reload stays the only way to pick up an edit to one Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Name the question callers actually ask with hasHostLink, rather than leaving every call site to spell out that an absent key means no project - Keep it off the wire, where the value it derives from is already present and a second field could only disagree - Take a session id on plugin add, since a session carries a host directory that was declared on the host and is the only other trustworthy answer to which project - Refuse a project id for the same job: a project is env-independent by design and its spaces name locations inside an env, so walking the host from one finds a stranger's project rather than failing - Refuse rather than fall back to the shell's directory, which would write a project nobody named - Raise session-not-linked when the session has no project, and say that linking is the remedy Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e package is staged - `options` now takes the host directory: staging lives under the cache, so a project `.npmrc` was never read and a private scope silently resolved against the public registry -- 404, or a public package of the same name. Threaded through `download`, `reify`, `probe`, `Store.add`/`update`. - Drop `--prefix`. npm pins its local prefix to one and stops walking, which would miss a token at a repository root while the person stands in `packages/x`. Without it npm walks up as it would if run there, which is the only rule a user can predict. - `probe` reads the same chain as the install; consulting a different registry than the fetch answers about a package nothing is ever installed from. - Answers Q2. Git auth needs nothing added: npm spawns git with `process.env` whenever the caller names no `env`, so the agent, `~/.gitconfig` and `~/.ssh` arrive by themselves. Verified against a real private repository over ssh and https, for `reify` as well as `probe`. - Tests: a project `.npmrc` two directories up is read and a staging one is not; `options` names no `env`, so a later prune cannot break private git silently; an unreachable remote fails rather than waiting for a password; the authenticated half is gated on `CODEWORK_LIVE_PRIVATE_GIT`. - Spec: §5.4.1 "Where auth comes from", Q2 closed, T10 recorded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…chine Adapted from opencode's npm fixtures, which serve a packument over loopback and `git init` a repository, so the real arborist and pacote paths are exercised with no network. - Their `.npmrc` tests hand the loader the directory holding the file, so they prove the loader and never that a call site passes a sensible directory -- which is why T10 is green there. Ours drives `Store.add`, so a regression to the staging path fails loudly. - `@fixture/plugin` exists on no public registry: an install that read the wrong `.npmrc` cannot quietly succeed against the real one. - Guards for `audit: false`, `allowGit: "root"` and git commit recovery, all hermetic; the live suite keeps its own copies against the public registry. - Every guard confirmed by breaking what it defends. Two needed a second pass: `ignore-scripts` is no longer enough to run a script on npm 12 (`allow-scripts`, npm/rfcs#868, blocks an unreviewed package), and arborist ignores a tarball's `scripts` unless the packument advertises `hasInstallScript`, so the first version passed without our flag set at all. It now turns off both npm gates from the project's own `.npmrc` and fails when `ignoreScripts: true` is removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… shell's directory `plugin add --session <id>` resolved the project to write into and left every other question anchored to the directory the command ran in -- which, for the server or UI the flag exists for, is not the project and often is not a project at all. - `plugin add ./tool --session <id>` imported `<cwd>/tool` while writing an entry naming `<project>/tool`: it validated one file and recorded another, or failed to find a path that was there all along. - The `.npmrc` chain came from the shell's directory too, so a private scope resolved without the token that lives in the project being written to. The same bug as the staging-directory one, a layer up: the write target was honest and nothing else was. - `from = linked ?? cwd` is resolved once, before the install, and `inspect`, `written` and `spellings` all take it. - Test drives the real CLI against a linked session from an unrelated directory, and fails with `plugin-not-found` against the old anchoring. - Spec: §12.7 no longer lists the `reload` trigger as deferred -- the server exists and `plugin.reload` is in the contract, exactly as that section predicted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… an .npmrc chain anchors Both consumers resolve one against `process.cwd()` in silence: `parse` would anchor `./plugin.ts` there, and npm answers a relative `cwd` with a `localPrefix` of whatever repository the process is inside -- measured, not assumed. For a server the process directory is nobody's project, so the result is a plausible answer to a question no one asked. - `rooted` guards `parse` and `options`; the check sits outside `tryPromise`, so it cannot be read as npm being unreachable and swallowed by the fallback to empty config. - A defect, not a typed error. Nothing a user types reaches it -- every reference goes through `parse` and every directory comes from `Global.resolve`, a resolved cwd or a session's `hostDir` -- so a relative one means a new call site got it wrong, and a stack trace names it. The whole suite passed unchanged, which is that claim tested. - Necessary, not sufficient, and the tests say so: the staging path was absolute and wrong for three months, and T7's two `/workspace/a` are both absolute and name different machines. This removes the spelling that is wrong silently, not the class of mistake. - Spec: T11, and a note in §5.4.1 on what absoluteness cannot supply. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t against the server's own `Session.create` and `Session.link` called `resolve` on the `hostDir` they were handed. The CLI already resolves against the shell's directory before sending, so that only ever fired for other clients -- and the only directory a server can resolve against is its own. Measured before the fix: a client asking to be linked to `my-project` was linked to `<the server's repository>/my-project`, branded `AbsolutePath` and persisted, with nothing reporting a problem. That field selects a settings file, and that file names plugins the server imports into its own process, so an invented path is not merely wrong configuration. - `rooted` moves to `util/path.ts`, now shared by plugin sources, npm config and sessions rather than copied; `AbsolutePath` is a bare brand with no check, so the assertion has to be explicit. - Declared means declared: absolute, or the caller has not said which directory it means. - Test links a session to a relative path over RPC and expects a refusal; it fails against the old resolving behaviour. - The whole suite passed unchanged, so no existing caller was relying on the resolve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- serialize concurrent plugin settings edits across CLI processes - validate and atomically replace runtime event definitions - preserve process-wide plugins across session follow and reload - cache and report the actual available plugin revision - add regression coverage for concurrency, reload, and cache behavior
- avoid creating project markers during plugin removal - preserve loader errors when validating plugin updates - exclude package configuration patches from lifecycle commands - keep git source identities collision-free - retain settings provenance during exchange-boundary loading
- key registry artifacts by effective registry and spec - fail closed when npm configuration files are unreadable - make sandbox host cwd explicit across controller boundaries - validate absolute paths in schemas and RPC payloads - add regressions for registry isolation, cwd propagation, and path decoding
…eclaring project names - Registry-keyed identity is now consistent end to end: install, resolve, check, update, boot load and exchange rebuilds all anchor a reference's `.npmrc` chain at the project that declared it, not the process's own directory, so a linked session no longer misses a private-registry entry or silently substitutes the public package. - `anchor` climbs from a settings file's `.codework/` directory to the project root, matching the boundary npm reads a project `.npmrc` at. - `Pool.Origin` records the declaring file so a retained module re-resolves through the same registry on union rebuilds; the session's own declarations win where both name one reference. - `plugin add` resolves its destination file before installing, so the artifact is keyed under the chain that will declare it, and a failed reference still leaves no `.codework/` marker behind. - An unreadable npm configuration now warns and continues with npm defaults instead of failing the install, matching npm's own behaviour. - Adds regression coverage for declaring-project anchoring, per-entry registry contexts in a shared pool, and the warn-and-continue path.
A retained origin -- a module loaded for another session that nobody still configures -- was unioned into every rebuild unconditionally. When its source vanished (a deleted file, a removed store entry) the union `load` failed, and since origins only update on a successful load, every later exchange and `plugin reload` kept failing until restart. - `follow` and `rebuild` now check each retained origin through `filed` before unioning; one that no longer resolves drops out of the pool with a warning naming the reference instead of failing the pass. - A session's own configured references stay strict: they are covered by the drift check and `load` still fails on them. - `known` (declaring-file anchors) is merged only from surviving origins. - A session that still configures a dropped reference sees it in the `missing` drift report with the `plugin install` remedy. - Regression test covers a dead retained origin dropping out while the current session's module loads.
An ephemeral kernel notice for each plugin pool transition, so listeners can observe module lifecycle without diffing the pool themselves. - `plugin.updated` carries `status: loaded | dropped | failed`, the configured reference, the plugin id when known, the declaring settings file, and the error message on failure. - Emitted from `activate` after the pool swap lands: `loaded` when a module instance changes (new plugin, superseding generation, re-imported local file), `dropped` when an origin leaves the pool. - `failed` publishes on a rejected `plugin reload` and on an exchange's `follow` error, with `reference` when the failure names one. - Ephemeral by design: settings and the store are the durable record; replaying transitions would only restate what the pool already says. - Kernel-owned type -- it lives in `EventList.Definitions`, so plugins cannot publish it and it cannot collide with a `plugin.<id>.*` type. - E2E test drives a real harness through loaded -> dropped -> failed.
…er layer only `Settings.load`'s `hostDir` is a session property by contract, but boot and `reload` passed the process's own `hostCwd` to it. That made the process behave like a session anchored at its launch directory: the project-layer discovery walk climbs ancestors, so a `.codework/` found anywhere above the launch directory silently configured the process -- a server started in /var/usr could pick up a stranger's project settings. - Boot and `rebuild` now load the user layer and the explicit override only; project plugins still arrive at a session's first exchange through `follow`'s union, and survive reloads through retained origins. - `--user-config-dir` is pre-resolved at the harness boundary: `~` expands to home and a relative spelling resolves against `hostCwd`, so a session `hostDir` can never move the override's anchor. - `hostCwd` doc corrected: it is the process-level directory (sandbox default, `.npmrc`/parse fallback, override base), not a project-settings root. - The resolve-only boot invariant moves to the user layer: an unresolvable user-layer entry still fails startup rather than fetching; a project-layer entry is now its session's drift report, not a process-level failure. - Tests pin a launch inside a project's subtree never importing its plugin (import side-effect sentinel), project entries surfacing as session drift, and relative `--user-config-dir` resolving against `hostCwd` with a different session `hostDir`.
Plugin authors had to depend on @codeworksh/harness to write a plugin, which meant installing the loader, the npm store, the control plane and the database to get at a handful of types. Extract the contract into its own package so a plugin package depends only on what it actually compiles against. The split is one rule: @codeworksh/plugin owns the contract, @codeworksh/harness owns the assembly. Moved out are the plugin definition (define, Plugin, Mount, SharedPluginContext, PluginRegistry, the `plugins` entry shapes), tool authoring (define/make/implement/register/provide, ToolProgress, ToolExecutionError), the sandbox mount and driver SDK, and the schemas they close over -- event, settings, location, project/space/repo, and the session and message id brands. Loading, installing, .npmrc anchoring, the executor, the registry buckets and the Location layers all stay in the harness. The harness keeps every module path it had: each vacated file is now a one-line re-export, so the ~80 files importing them are untouched and @codeworksh/harness /effect and /sandbox still resolve. Only location.ts and plugin/context.ts split rather than move whole, keeping their layers and the kernel-event guard. Net +70/-2368 in packages/harness/src, no behaviour change. The harness externalizes the SDK in both its .d.mts and .mjs output rather than inlining it, so a process holds one SandboxIO.Shell tag rather than two. Service tag keys move to @codeworksh/plugin/* as the deterministicKeys diagnostic requires; they are runtime-only and nothing persists them. The root entry exports Plugin and Tool and nothing else. The other subpaths are published because the harness imports them across the package boundary, not as an authoring surface, and are deliberately not re-exported from it. The four extras/ examples now build against @codeworksh/plugin instead of the harness, which is what demonstrates the package stands on its own.
`src/sandbox.ts` re-exported `@codeworksh/plugin/sandbox` with `export *`. It is also a pack entry, and a bundler cannot know what an external star-export provides, so it emitted an empty namespace object: the daytona and vercel drivers, which import the barrel by name, read `SandboxDriver.AbsolutePath` off `undefined` and threw on load. Only the published package was affected. The suite runs from source through vite aliases, where the barrel resolves normally, so nothing caught it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A plugin is installed into the store as its own npm tree, so `effect` arrives there a second time. Two copies of the same version are two module instances, and they do not agree: a schema carrying a check rejects every value, and the turn dies committing the assistant part with `SchemaError: Expected JSON value at ["data"]["part"]`. A real plugin whose success type was `Schema.Finite` registered, reached the model, ran its handler, and then failed with "Expected a finite number" for 22800. Deduplicate at resolution: while a plugin is imported, `effect` and `@codeworksh/plugin` resolve as though this module had imported them. A symlink into the store cannot do this — a generation is immutable and shared by every project on the machine, while the harness doing the loading is whichever one is running now. The fixture that was supposed to cover this proved nothing. It took its second instance by re-importing `effect` under a query string, and `effect`'s entry re-exports from submodules whose specifiers carry no query, so they resolved to the modules already loaded and `foreign.Schema === Schema`. It now tags the whole subgraph, which is what a separate install produces — and with a real second instance the old claim that one version "interoperates completely" turns out to be false. The tests say what actually happens instead. The same failure was attributed to effect@4.0.0-beta.107, i.e. to a version difference. It reproduces at the pinned version with only the instance separated, so the variable was never the version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ate work
`await import("pacote")` puts `resolve` and `manifest` on `default`: pacote is
CommonJS and builds `module.exports` in a way Node's named-export detection does
not see through. `probe` called them off the namespace, threw
`TypeError: pacote.resolve is not a function`, caught it, and reported
`plugin-resolve-failed: cannot reach <spec>` — blaming the network for a
programming error. `plugin check` and `plugin update` had therefore never
worked, for git or registry targets.
The ambient declaration named them as named exports, which is why this
type-checked. It now declares the default export that actually exists, so the
same mistake fails at compile time.
`plugin.npm.live.test.ts` already covers this — it probes a mutable branch and
asserts a 40-hex sha. It is gated behind CODEWORK_PLUGIN_LIVE=1 and had not run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mpiled A plugin distributed over git or npm has to ship built javascript, for two reasons that compound: installs run with lifecycle scripts disabled, so a `prepare` build never happens, and Node refuses to strip types under `node_modules`. Until now that surfaced as the generic `plugin-import-failed`, which tells an author their plugin is broken but not what to do about it. The remedy is distinct — it belongs to the author's build, not to the user's settings and not to `plugin install` — which is the bar this taxonomy sets for earning a slug, and the same one `plugin-missing-dependency` cleared. Matched on `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` rather than on the message: the code is Node's documented identifier and exact. A local plugin is outside `node_modules`, where types strip happily, so the single-file dev workflow is unaffected — the test asserts both sides. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`check` reuses an answer for an hour. That is right for a command someone runs repeatedly and wrong for the minute after they pushed a plugin, where it reports a moving branch as current while `update` — which always refreshes — moves it. There was no way to ask again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every exports entry carried `"development": "./src/*.ts"`, and the tarball is built from `dist/pack` with no sources in it. A consumer resolving under that condition — vite dev, vitest, or this repo's own `start` script, which passes `--conditions=development` — got ERR_MODULE_NOT_FOUND for a package that installed cleanly. It affected every published package, not just the new one. Publishing a condition the artifact cannot satisfy is never right, so it is removed rather than repointed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two plugins published the way a third party publishes them: their own private repositories, their own release tags, built output committed, and `@codeworksh/plugin` taken from npm rather than from this workspace. The specs are cloned from github, installed by arborist into a real store, imported by the real loader, and run in a real session. It is the only place the contract crossing the package boundary is the shipped one, and the only place a tool whose success type carries a check runs from an installed plugin — which is what makes it the end-to-end guard on the effect deduplication. Needs github and npm, plus credentials for the private repositories. Opt in with CODEWORK_PLUGIN_LIVE=1, like the other live suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`session link` read "Point a session at the host directory its settings come from", and the server contract said much the same. That defines the field by one of its readers: settings discovery reads the anchor, so do plugin installs, and so will whatever needs a host-side directory next. Anyone reading the help came away thinking `hostDir` is a settings flag. It is an anchor to a host directory for the session, and the wording now says only that. The path argument names `.codework` as a landmark so the directory is still recognisable, which is what COD-67 asked for, without making the anchor mean configuration. Closes COD-67 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sable The CLI is the outer layer, so it says the thing a person can recognise: the path argument suggests the directory holding your .codework configuration, and an example links a project elsewhere on the machine. It stays a suggestion rather than a definition -- "typically", named once, on the argument. `hostDir` is still an anchor and the contract still says only that; what reads it remains the reader's business. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.