Skip to content

Rewrite the third-party extensions design against the real catalog - #186

Merged
jasperf merged 1 commit into
mainfrom
docs/third-party-extensions-rewrite
Aug 7, 2026
Merged

Rewrite the third-party extensions design against the real catalog#186
jasperf merged 1 commit into
mainfrom
docs/third-party-extensions-rewrite

Conversation

@jasperf

@jasperf jasperf commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

docs/third-party-extensions.md (added in d498c3e) surveyed four generic plugin architectures and recommended one without reference to how wp-ops actually resolves and runs a command. This rewrites it as a single named decision — build-time embedded catalog vs. runtime discovery — with the code that decision breaks cited by file and line.

Documentation only. No code changes.

Findings the first draft missed

  • Entry has no source root. ScriptPath is repo-relative and joined against repoRoot() at six sites — dispatch.go's four executors, docs.go's @doc resolution, and manifest.Lint's doc check. Nothing external is runnable before that changes, and it is the whole of Phase 1.
  • The binary embeds its own asset tree and extracts it to a per-version cache dir (env.go:45-111), so a Homebrew install has no checkout on disk. "Drop your script into the repo" serves the least common install shape.
  • catalog.go states "no filesystem scan happens at runtime" as an invariant, and runtime discovery inverts it. Restated as a two-tier rule: core keeps its build-time guarantee, a malformed extension is skipped with a warning and can never be fatal.
  • A @category outside DisplayOrder makes a command invisible in every listing and the picker, since DisplayCategories() filters against that hardcoded slice. Falls back to misc.
  • mcp-server reads catalog.json off disk from REPO_ROOT, so extensions are invisible to MCP. Now an explicit decision with a recommendation rather than an omission.
  • Completion runs the scan on every <TAB>, so the mtime cache is load-bearing, not an optimization. Budgeted at 5ms with a stated fallback if it can't be met.

Dropped, with reasons recorded

Each entry below is in an out-of-scope table in the document, so the removals stay reviewable in a diff that deletes 645 lines.

Dropped Why
Four duplicate-prevention strategies ext/<vendor>/ keys make full-key collisions structurally impossible; basename collisions are already handled by ShortName and printAmbiguous
permissions: sandbox block Unenforceable against an execd shell script — documenting controls that don't exist is worse than documenting none
Package manager and registry git clone covers distribution; search needs a registry that doesn't exist
Test-helper library, test generate-ci Core scripts aren't tested either; ext validate covers the real failure mode

Structure

Adds an up-front "Is this needed?" section separating three possible demands (private client scripts / shareable packs / public ecosystem) with very different price tags, since Phase 1 is worth doing regardless and Phases 2–3 are not. Adds a consumers-that-must-change checklist. Leaves four open questions on the record rather than answering them.

Review notes

This is a design proposal, so the Entry.Root sketch and the 5ms completion budget are claims to test during implementation, not measurements. The MCP question (CLI-only extensions vs. mirroring discovery in TypeScript) is the one decision worth settling before Phase 1 starts.

The first draft surveyed four generic plugin architectures and recommended
one without reference to how wp-ops actually resolves and runs a command.
Rewrite it as a single decision — build-time embedded catalog vs. runtime
discovery — with the code that decision breaks named by file and line.

The substantive findings the draft missed:

- Entry has no source root. ScriptPath is repo-relative and joined against
  repoRoot() at six sites (dispatch.go's four executors, docs.go's @doc
  resolution, manifest.Lint's doc check). Nothing external is runnable
  before that changes, and it is the whole of Phase 1.
- The binary embeds its own asset tree and extracts it to a cache dir, so a
  Homebrew install has no checkout at all — "drop your script into the repo"
  serves the least common install shape.
- catalog.go states "no filesystem scan happens at runtime" as an invariant;
  runtime discovery inverts it. Restated as a two-tier rule so core keeps
  its build-time guarantee and a malformed extension can never be fatal.
- A @category outside DisplayOrder makes a command invisible in every listing
  and the picker. Falls back to misc.
- mcp-server reads catalog.json off disk from REPO_ROOT, so extensions are
  invisible to MCP. Made an explicit decision rather than an omission.
- Completion runs the scan on every <TAB>, so the mtime cache is load-bearing;
  budgeted at 5ms.

Dropped, each with a reason recorded in an out-of-scope table: the four
duplicate-prevention strategies (ext/<vendor>/ keys make full-key collisions
impossible, and basename collisions are already handled by ShortName and
printAmbiguous), the permissions sandbox block (unenforceable against an
exec'd shell script), the package manager and registry, and the test-helper
library (core scripts aren't tested either — ext validate covers the real
failure mode).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jasperf
jasperf merged commit 19e44d7 into main Aug 7, 2026
1 check passed
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