Collect string-valued manifest.hooks as a file reference so missing files, directories, and paths outside the cache fail validation. Add regression coverage for valid and invalid hook references while preserving non-string and absent-hook behavior.
Closes#3786
getopt_long accepts a unique prefix, so sudo --use root rm -rf build is
--user root and the resolver read root as the command. Expand unique
prefixes from sudo's long option table before the value/non-exec checks.
--help joins the non-executing modes.
sudo accepts VAR=value between its options (parse_args.c is_envar), so
sudo FOO=1 -u root rm -rf resolved -u as the command. Keep reading options
past assignments. Add sudo -R/--chroot, -a/--auth-type, -c/--login-class
and doas -a as value flags, and stop a short-cluster scan at any unknown
letter, so sudo -R/live rm -rf no longer reads the l in /live as -l.
doas -C only checks config, so it is non-executing; the existing doas -C dd
case moves to the passive list.
sudo -l/--list, -v, -K, -V and -e/--edit (and doas -L) do not run the
command operand, so sudo -l rm -rf build was denied as destructive. The
privilege-wrapper resolver now reports no executable command for those
modes. Short clusters stop at the first value flag, so sudo -ul rm -rf
(user l) stays destructive.
- Remove the unused `test` helper: `eslint` fails this file with
no-unused-vars ("'test' is defined but never used", exit 1).
- Print `Results: Passed: N, Failed: N`. tests/run-all.js parses those tokens
for repository-wide totals, so the suite previously contributed 0 tests.
- Remove the generated temp plugin directory after each import instead of
leaving an `ecc-plugin-contract-*` directory per run.
node tests/opencode-plugin-loader-contract.test.js -> Results: Passed: 3, Failed: 0
node tests/run-all.js opencode-plugin-loader-contract -> Total Tests: 3, Passed: 3
npx eslint on all six changed files -> 0 errors
The inert stub written when hook consent is not granted was
`export default async () => ({})`. OpenCode v2 validates every plugin
module before running it and requires the default export to be a definition
object with an `id` and an `effect` or `setup` function, so a declined or
runtime-less home install printed the same startup error as the V1 hook
plugin:
PluginModule.LoadError: Plugin must export a default definition with an id
and an effect or setup function.
(cause: SchemaError(Expected object at ["default"]))
Write `export default { id: "ecc-hooks", setup() {} }` instead. It keeps the
inert behaviour (no imports, no module initialization of the replaced
plugin) and satisfies the loader.
Tests that pinned the generated bytes now compare against
getDisabledOpenCodePluginContent() rather than a duplicated literal, and
tests/opencode-plugin-loader-contract.test.js asserts the loader contract on
the generated payload. That test fails on the previous stub
("default export must be an object, got function").
Refs #3537. The same assertion should cover .opencode/plugins/*.ts once a V2
entrypoint lands (#3536, #3224); those plugins are scanned independently in
home installs, so both the barrel and the implementation are loaded.