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.
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.
Address CodeRabbit re-review on #3771: rewriteRuleLinks now tracks fenced
code blocks (``` and ~~~) line by line and only rewrites links outside
them, so Markdown examples shown inside fences stay verbatim. One shipped
rule contains such an example, so this changes installed output today.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address CodeRabbit review on #3771:
- Treat immediately empty (`---\n---`) and whitespace-only frontmatter
blocks as empty metadata instead of throwing or leaking the delimiters
into the installed body.
- Reject an empty `paths: []` list before serialization so an installed
instruction never receives an empty `applyTo:` glob.
- Rewrite relative Markdown links that resolve inside the installed rule
set to their renamed `.instructions.md` targets; links escaping rules/,
external URLs, absolute paths, anchors, and README.md stay unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The copilot project target installed only skills and agents, dropping the
entire rules/ tree even though Copilot CLI and VS Code read path-specific
instructions from .github/instructions/**/*.instructions.md.
Plan each rules/**/*.md file into .github/instructions/ecc/ as an
.instructions.md file and add a copilot-rule-frontmatter transform that maps
the Claude-style `paths:` list onto Copilot's `applyTo:` glob list. Rules
without `paths:` become applyTo: "**", matching their always-on semantics in
Claude Code. rules/README.md documents the rule set and is not installed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
isDestructiveRm/isDestructiveGit keyed on argv[0], so sudo rm -rf,
command rm -rf and env git reset --hard read as calls to the wrapper and
were allowed once the routine shell gate was satisfied. Run both checks on
the unwrapLeadWrappers argv too, the resolution the dd and sh -c paths
already use. Overlaps #2832, which also reworks sh -c parsing and has
conflicts with main; this is the minimal rm/Git part.