Carry forward PR #3055 at 92f53c4c onto ef648e01, preserving contributor attribution and current main. Focused maintainer fixes reuse shared input ceilings, preserve late root context through outer bootstrap, bound frontmatter before immutable parsing, and assert real oversized input in the limits workflow.
plugin-hook-bootstrap (shell mode) and observe-runner probed a bare
`bash.exe`. On Windows that resolves to the first match on PATH, and
%SystemRoot%\System32 normally sits ahead of Git's bin directory, so the
pick was System32\bash.exe: the WSL launcher. The existing guard (require
a clean probe exit) only catches it when no distro is installed. With one
installed, the probe passes inside Linux, then the hook script is handed
over by a Windows path (C:\...) or a Git Bash path (/c/...), neither of
which exists in WSL. The hook exits 127 instead of running, and
observe-runner's continuous-learning observation fails on every call.
Resolve bash candidates on Windows to absolute paths from PATH, skipping
bash.exe in System32, SysWOW64, Sysnative, and the WindowsApps alias
directory, so a native bash later on PATH (Git's) is used. With none
available, the existing "no bash binary found" skip applies. An explicit
BASH override still wins, and other platforms keep bare names.
The shared resolver lives in scripts/lib/windows-bash.js with a
platform-independent test. A Windows integration test puts System32 ahead
of Git's bash and requires the .sh hook to actually run. Upstream's
bootstrap fails that scenario with exit 127 ("/bin/bash: C:Users...hook.sh:
No such file or directory"); this one prints the hook output.
Carry forward the final scoped delta of PR #3119 at 60e82f95. Keep required dependency closure and select only modules reachable from successful requests; refresh the unchanged 293-skill registry binding.
Carry forward PR #3115ef475c79 and 780dfb78 onto current main. Preserve the newer hook-consent transforms during install and repair. Main already includes the source import fix from #3167.
Keep the operation lock while validating bigint file identity and reuse
only snapshots produced by successful trusted static SELECTs. Avoid
reloading and exporting unchanged databases on repeated public reads.
Retain conservative comparison for generic SQL, including RETURNING and
PRAGMA writes, and invalidate reuse after transactions, exports or errors.
Cover cache invalidation, cross-process visibility, Windows device IDs,
reentrant partial writes, lock contention and independent result objects.
Validate 188 focused tests per environment on WSL Node 22.22.3 and native
Windows Node 22.15.0/24.16.0, plus 38 related WSL tests. Paired warm-read
benchmarks show 7.6-18.1x point-query speedup on 160 KiB/1.28 MiB databases.
Handle Windows Node path stats that report dev=0 for the same file whose
open descriptor reports a volume serial. Keep bigint inode checks strict,
retain device comparisons whenever both are known, and leave POSIX strict.
Cover zero-device identity cases, high inode precision, replacement and
file-type safeguards. Verify 164 focused tests each on native Windows
Node 22.15.0 and 24.16.0, plus WSL Ubuntu Node 22.22.3.
Compare the completed snapshot after get/all queries so RETURNING and
prepared PRAGMA writes survive reloads and reopening. Export only after
the outer operation or transaction succeeds; unchanged read snapshots
still avoid disk writes. This adds one export/byte comparison per read
snapshot without relying on SQL parsing or private sql.js APIs.
Add 18 regression cases covering multi-row INSERT/UPDATE/DELETE RETURNING,
standalone and transactional get/all, no-row PRAGMA writes, and rollback.
The unfixed adapter fails 16 of these cases. Strengthen read-only checks.
Validation: 148 focused tests pass on WSL Node 22 and Windows Node 24;
the general state-store suite passes 20/20 on WSL and 19/20 on Windows,
where the existing file-symlink fixture is denied by Windows permissions.
Scoped adapter coverage is 90.13% lines, 82.73% branches, 100% functions.
ESLint, Unicode safety, and git diff --check pass.
Three review findings, all verified before fixing.
1. Publish surface (blocking). package.json `files` lists
scripts/gemini-adapt-agents.js but not this script, so the npm tarball
omitted it and ecc-universal users hit MODULE_NOT_FOUND. Added to `files`
and to the allowlist in tests/scripts/npm-publish-surface.test.js, which now
passes 2/2.
2. CRLF agents were silently skipped. The frontmatter regex matched LF only, so
a Windows-saved agent was reported as 'already compatible' while still
carrying tools: Read, Bash and color: teal -- a success message with no
adaptation. Normalize to LF, adapt, then restore the original ending style.
Reproduced before and after: the same CRLF fixture went from 'Updated 0' with
Read/teal intact to 'Updated 1' with read_file/cyan and CRLF preserved.
3. Unknown flags were discarded. Because the script rewrites files in place,
`--dry-run` rewrote every installed agent and exited 0. parseArgs now throws
on any unrecognized option before a directory is selected.
Tests: 12/12 (added CRLF preservation and no-mutation-on-unknown-flag),
gemini-adapt-agents 4/4 unaffected, npm-publish-surface 2/2. Re-run against all
68 canonical definitions: 68 updated, second run 0 updated, no Claude-shaped
frontmatter remaining, no spurious CRLF introduced.
Note: findings 2 and 3 apply equally to scripts/gemini-adapt-agents.js, which
has the same LF-only regex and the same flag filter. Left unchanged here to keep
this PR scoped to the Qwen adapter; worth a follow-up.
Review correction. The original version dropped WebSearch on the claim that Qwen
Code has no web search tool. That claim is wrong: Qwen Code exposes web_search as
a deferred tool, resolving through a DashScope search agent.
web_search is provider-dependent rather than universally present, but mapping is
still the better default than dropping. Where the provider supports it the agent
gains the capability; where it does not, Qwen Code ignores an unresolvable entry
in tools: without warning, so the outcome is the same as dropping. The reasoning
is recorded in a comment at the mapping so the choice is not silently reverted.
Across the 68 canonical definitions this changes two agents from losing a
declared capability to keeping it. Tests updated to assert the mapping; 10/10
pass and gemini-adapt-agents.test.js still passes 4/4.
--target qwen installs all 68 agent definitions verbatim, so they carry Claude
Code tool names (Read, Write, Edit, Bash, Grep, Glob, WebSearch) and color: teal.
Qwen Code uses different tool ids, and its palette is red/blue/green/yellow/
purple/orange/pink/cyan/auto, so an unlisted value is dropped at load time with a
SUBAGENT_MANAGER warning.
ECC already solves this for Gemini via scripts/gemini-adapt-agents.js; this adds
the Qwen counterpart plus a test mirroring the Gemini one. The canonical agents/
directory is untouched -- each harness adapts its own installed copy.
Deliberate differences from the Gemini adapter, each a real Qwen behaviour:
Edit -> edit (Gemini maps to replace)
WebSearch -> dropped (Qwen has web_fetch only, no web search)
mcp__server__tool -> unchanged (Gemini lowercases to mcp_server_tool)
color: -> remapped (Gemini removes it; teal becomes cyan)
tools: output -> comma scalar (the form Qwen's own examples use)
model: is left alone, matching the Gemini adapter. 58 files say sonnet, 6 haiku,
4 opus and none are Qwen model ids, but Qwen logs no warning for them, so whether
they are ignored or resolved at dispatch time is unverified and changing 68 files
on an assumption seemed worse than flagging it as a follow-up.
Tested: 10/10 new cases pass and gemini-adapt-agents.test.js still passes 4/4.
Run against a copy of all 68 real definitions: 68 updated, 0 bodies changed,
0 invalid frontmatters, second run reports 0 updated. The result was installed
into ~/.qwen/agents and a headless Qwen session loaded all 68 with no errors and
no color warnings.
LANGUAGE_RULES had no entry for Dart, and FRAMEWORK_RULES had no way to
detect Flutter, so any Flutter app (pubspec.yaml, lib/*.dart, android/,
ios/) was reported as primary: "unknown" by the SessionStart project-type
banner, despite ECC shipping a dart-flutter-patterns skill and a
dart-build-resolver agent for this exact stack.
Adds a 'dart' language rule (pubspec.yaml/pubspec.lock markers, .dart
extension) and a 'flutter' framework rule that checks pubspec.yaml for
the standard `flutter: / sdk: flutter` dependency shape, via a new
customCheck hook on FRAMEWORK_RULES entries. This distinguishes Flutter
apps from plain Dart packages, which don't depend on the Flutter SDK.
Closes#3469
Add Arabic (ar) localization guide and glossary, register the locale:ar component and docs-ar module across install manifests, and wire test coverage.
Contributed-by: @taljeri (taljeri)
Signed-off-by: taljeri <318237806+taljeri@users.noreply.github.com>
The ecc@ecc plugin namespaces agents, commands and skills as `ecc:<name>`.
A manual install (install-apply --target claude|claude-project) places the
same files at bare names under ~/.claude, so plugin-prefixed references in
copied markdown point at agents and commands that do not exist in that
layout (e.g. subagent_type "ecc:planner", frontmatter agent: ecc:...).
- Add plugin-namespace.js with stripPluginNamespace(), which removes the
`ecc:` prefix before an identifier and leaves `ecc@ecc`, URLs and words
ending in "ecc" alone.
- Apply it to markdown copy operations when the target is claude or
claude-project. Non-markdown files and other targets are unchanged.
- Reword the agents rule so it describes both install modes instead of
stating that agents only ship with the plugin.
- Cover the transform with tests/lib/install-plugin-namespace.test.js.
/astra-review now runs on Opus (model: opus). Opus verifies findings,
checks every diff, and runs the tests; fixes go to a Fable subagent.
When that Agent call fails with a rate_limit / HTTP 429 API error, the
command switches to Astra for the rest of the session:
- astra-review.js --fix-findings <file> sends confirmed findings to
Codex in a workspace-write sandbox with the review isolation intact
(no web search, MCP disabled, user config ignored, no API keys) and
/tmp, $TMPDIR, and the network excluded from the writable roots.
- New scripts/lib/astra-review/fix.js builds the fix prompt, validates
the structured fix result, and formats the report.
- codex.js shares one exec runner between review and fix; the sandbox
is allowlisted (danger-full-access is refused).
- The command snapshots git diff and untracked files before any writer
runs, so Opus reverts only the writer's hunks and never the user's
uncommitted work.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Under OpenCode the skill-health dashboard had no writer and no honest
fallback, so it reported every discovered skill as healthy (#2463).
Root cause: `post:skill:track` is only wired for Claude Code, where
`posttooluse-dispatcher.js` fans the event out to `skill-run-tracker.js`.
The OpenCode plugin re-implements the harness events instead of reusing that
dispatcher, so nothing ever wrote `skill-runs.jsonl` — and the tracker's
matcher only accepted the exact Claude Code tool name `Skill`, while OpenCode
calls the tool `skill`, so even wiring it would have dropped every event.
Commands that locate ECC through the inlined resolver in
`scripts/lib/resolve-ecc-root.js` also fell back to `~/.claude`, which does
not contain ECC, so they could not find their scripts.
Changes:
- `.opencode/plugins/ecc-hooks.ts`: dispatch `post:skill:track` from
`tool.execute.after`. The tracker module is imported lazily and behind
try/catch, matching the existing `changed-files-store` pattern: this file is
OpenCode's startup entry point, so a static import failure would crash the
session before any hook runs (#2530). The module is reused rather than
reimplemented, keeping its identifier validation, length bounds and
"never persist prompt text" guarantees.
- `scripts/hooks/skill-run-tracker.js`: match the tool name case-insensitively
so `Skill` and `skill` are both accepted.
- `scripts/lib/skill-evolution/health.js`: split the summary into
measured/healthy/declining/unmeasured instead of deriving `healthy = total -
declining`, which turned "no telemetry" into "everything is fine" for every
skill.
- `scripts/lib/skill-evolution/dashboard.js`: show the breakdown and warn
explicitly when skills have no recorded runs, since absence of telemetry is
not a health signal.
Under OpenCode the resolver also needs `CLAUDE_PLUGIN_ROOT`, which only the
plugin can supply: `shell.env` exports it when a root is found, without
replacing a root the user set or one an earlier plugin already put in
`output.env`.
Root discovery is per capability and probes required runtime files — never
optional content. Probing for `skills/continuous-learning-v2` (the
`workflow-quality` module) rejected supported modular installs that ship the
plugin and tracker without that optional module, disabling telemetry and the
root export for exactly those users.
Also fixes an incidental bug: the compiled plugin bundle is ESM, where
`__dirname` is undefined, so the ECC version lookup silently fell back to its
default. The module directory is now derived from `import.meta.url`.
Tests: `tests/opencode-plugin-hooks.test.js` covers the telemetry dispatch
(skill tool recorded, other tools ignored, minimal profile respected), a
modular install without the optional skills module, the root export and both
override paths; `tests/hooks/skill-run-tracker.test.js` covers the
case-insensitive matcher; `tests/lib/skill-evolution.test.js` and
`tests/lib/skill-dashboard.test.js` cover the unmeasured bucket, the warning
and the aged-out-coverage wording.
Refs #2463, #2530
The catalog gate validated only the first README project-tree block and
had no skills/commands counterpart to the agents pattern, so:
- README.md project tree (skills) and (commands) were never checked.
- A stale count in a second tree block was invisible because the agents
pattern used String.match with a non-global regex, which reads only the
first occurrence.
Both are now fail-closed. parseReadmeExpectations walks every tree block
for all three categories via matchAll, reports the offending line number
in the source label, and throws when an entry is missing entirely rather
than skipping it. syncEnglishReadme uses replaceAllOrThrow so --write
repairs every block instead of only the first; replaceOrThrow could not
be reused because it replaces only the first match and its regex.test()
probe is unsafe once the pattern carries the g flag.
README.md:847 documented 67 agents against an actual 68 and was never
checked, so it was corrected.
Fixtures in tests/ci/catalog.test.js and tests/ci/validators.test.js now
emit the skills and commands tree lines the gate requires, and both gain
regression coverage that fails against the previous parser (5 new tests).
main resolves SQL clients behind env -S and its launcher grammars, but a
destructive statement still passed under four forms:
- a shell -c inside a short-option cluster (bash -lc, sh -ec), since only
an exact -c was looked up;
- su, whose -c / --command / -lc value runs through the target user's
shell, before or after --;
- taskset, whose mask or CPU list precedes the command, like timeout's
duration;
- a command nested past the shell -c recursion limit, which was allowed
instead of denied.
The first three are checked like sh -c; past the limit the check now
fails closed.
The previous fix stopped a throwing hook from aborting the cold replay by
recording it as an allow, matching the warm path. But coldSummary only counted
disagreements, so a failure on a step the worker also allowed was no
disagreement and left no trace: the report showed no failure, a run with no
measured step reported a cold latency of 0 ms because percentile of an empty
list is 0, and the exit status passed. Trading a crash for a clean-looking
success hid exactly the failure the crash had exposed.
coldSummary now counts errors and samples, and reports no latency when nothing
was measured rather than a 0 that reads as a fast hook. The report prints "no
valid sample" in place of a latency and adds a line for steps that failed in a
fresh process, beside the existing disagreement line. The exit check moves into
gateFails, which fails on cold errors as it already did on warm errors and cold
disagreements, and is exported with coldSummary so both can be tested.
The render test builds its report from summarize([]), the real summary shape,
so it keeps working as metrics are added.
measureColdLatency ran each step in a fresh process through execFileSync,
inside try/finally with no catch. A hook that threw made the child exit
non-zero, execFileSync threw, and the error left measureColdLatency and then
evaluate, so neither the report nor the requested SARIF was written for a run
that had hit a single bad step.
The probe now catches a throwing require or run itself and records it the way
workerMain does: the raw payload stands in for the result, so the step reads as
an allow, and the error is kept. Matching the warm path matters because
coldSummary counts every cold decision that differs from the warm one as a
disagreement; any other choice would have reported disagreements that do not
exist. A child that dies without writing parseable output is caught outside the
probe and recorded the same way with no latency, and coldSummary leaves
unmeasured steps out of the percentiles while still counting them.
The new tests run a hook that always throws through both passes. They fail
against the previous code, 2 of 17, and pass against this one.
Brings in the three commits main gained since the branch's merge base: the 2.2.3
release, retiring the Everything Claude Code name for ECC, and the ja-JP sponsor
update. 227 files arrive from main; two conflicted.
CHANGELOG.md: the branch had added an Unreleased section and main a released
2.2.3 section, both at the top. Both are kept verbatim with Unreleased above
2.2.3, since none of the branch's entries shipped in that release.
manifests/context-packs/skill-triggers@1.json: both sides had recomputed
registryDigest from the same earlier value to different results, and neither is
the digest of the merged tree. It is recomputed with loadContextRegistry over the
merged tree and only that field changes; triggers and triggersDigest are
untouched on both sides. validate-context-profiles passes.
All twelve CI validators pass. The full suite reports 44 failures in seven files;
43 of them fail by name and count identically on an unmodified upstream/main, and
the remaining one, plugin-hook-bootstrap, fails only inside run-all and passes in
isolation on both trees. The merge introduces no failing test that main does not
already have.