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.
save.mjs derived contextDir by stripping everything outside [a-z0-9-],
so a project name with no ASCII letters or digits (e.g. "Проект")
produced an empty contextDir. forget.mjs then resolved
resolve(CONTEXTS_DIR, "") to CONTEXTS_DIR itself and removed it
recursively, deleting every other project's context.
- save.mjs: fall back to project-<sha256(path)[:8]> when the slug is
empty, so contextDir is never "".
- forget.mjs: refuse to rmSync unless the resolved path is strictly
inside CONTEXTS_DIR. This also protects registries that already
contain an empty contextDir.
- tests/skills/ck-forget.test.js: regression tests for both cases.
- manifests/context-packs/skill-triggers@1.json: refresh registryDigest,
which validate-context-profiles requires after any skills/ change
(same as 9fdf1ca2).
Signed-off-by: Wen <jwcastillo@gmail.com>