Adds a skill + matching delegated agent that draft Conventional
Commits-style messages by reading the repo's own git log norms
(prefix, scope, subject length) instead of assuming a fixed format.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Preserve PR #2968 contributor history, verified upstream claims, and target-specific metadata. Rebind the existing trigger registry metadata after the merge, retaining current trigger phrases and generation provenance. Portable Bun 1.4.2 isolation and parallel-timing examples passed; all five Markdown files and context profiles validated.
Preserve PR #2943 contributor history and rebind the existing trigger manifest to the 294-skill registry without changing trigger phrases or model provenance. Validated real read-only audit CLI, 66 focused Python tests, 239 full Python tests, five Node surface checks, catalog, install manifests, and context projections.
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.
On a Windows machine with Claude Code installed natively, Git for Windows,
and the ECC plugin installed, `node tests/run-all.js` on main fails 44 tests
in 7 files. Linux CI never sees them. Three of the four causes are test
isolation gaps, fixed here (43 tests):
- claude-plugin-setup, claude-scope-migration, setup (38): fixtures put a
fake claude.cmd first on PATH, but on Windows spawning a bare `claude`
resolves only .com/.exe, so a native ~/.local/bin/claude.exe ran instead
of the fake. Fixture PATHs now drop entries holding claude.exe/claude.com.
- ecc-universal-bin, eval-harness-package (4): tar received an absolute
C:\ archive path, which Git Bash's GNU tar reads as a host:path remote
spec ("Cannot connect to C: resolve failed"). Extract from inside the
destination with a relative, forward-slash archive name, which both GNU
tar and bsdtar accept.
- harness-audit (1): the cache-layout test overrode HOME only, while
findPluginInstall also reads USERPROFILE and os.homedir(), so the real
installed plugin won over the fixture. Override USERPROFILE too.
The two helpers live in tests/lib/helpers/windows-test-env.js with a
platform-independent test, so Linux CI holds both invariants too.
The 44th failure, plugin-hook-bootstrap's no-bash test, is a production
bug rather than a test leak: System32\bash.exe launches an installed WSL
distro, which passes the bash probe. It gets a separate fix.
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 corrections, each verified against an installed Qwen Code 0.25.0:
1. web_search does exist. The mapping table said WebSearch had no Qwen
equivalent; Qwen Code exposes web_search as a deferred tool backed by a
DashScope search agent. Now qualified by provider, with web_fetch as the
fallback, and web_search added to the deferred-tools sentence.
2. docs/QWEN-GUIDE.md is not installed. The installer copies .qwen/QWEN.md to
~/.qwen/ but does not copy docs/, so a relative reference to the guide is dead
in any session outside an ECC checkout. Now states that explicitly and links
the guide in the repository.
3. The AGENTS.md loading claim is now version-qualified. It is accurate for
0.25.0 under default settings -- with no context.fileName configured, Qwen's
own debug log resolves "Final ordered QWEN.md,AGENTS.md paths to read" to
both files -- but AGENTS.md is not the documented default context filename, so
the sentence no longer asserts unconditional behaviour and points at
context.fileName instead.
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.
The existing frontmatter test only checks that a description is present, so all
three array-valued argument-hint declarations passed CI on main. Harnesses that
type-check the field reject the whole file, which is how the commands went
missing without anyone noticing.
Add an assertion scoped to argument-hint only. A blanket 'no flow sequences in
frontmatter' rule would be wrong: allowed-tools is a legitimate list in
marketing-campaign.md and skill-create.md.
Verified both directions -- on main the new test fails and names exactly
code-review.md, cost-report.md, and orch-review.md; on this branch the suite
passes 96/96.
--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
`.qwen/QWEN.md` is copied to `~/.qwen/QWEN.md`, which Qwen Code loads as
home-level agent context in every session. Its current content describes the
install template itself -- 'This directory contains ECC's Qwen CLI install
template', the `./install.sh` invocation, and the managed-module list -- so
every Qwen session spends its context budget on installer documentation while
getting none of the baseline workflow, coding standards, or security checklist.
Every other harness target ships real agent context here: `.gemini/GEMINI.md`
('provides Gemini CLI with the baseline ECC workflow, review standards, and
security checks') and `.codex/AGENTS.md` ('supplements the root AGENTS.md with
Codex-specific guidance'). Qwen was the only target whose context file described
the installer.
Rewrite it against the GEMINI.md structure and add two Qwen-specific sections:
- A Claude-tool-name to Qwen-tool-id mapping. ECC skills, rules, and agent
prompts are authored for Claude Code, so a Qwen agent following them needs the
translation to act correctly.
- An explicit 'Not Available On This Target' note. Hook runtime files are not
selected for Qwen, so an agent must not report a hook-enforced guard or
cost-tracking metric as active. Also records that mcp-configs/ is a baseline
catalog rather than a merged settings file.
The displaced install documentation is not lost: docs/QWEN-GUIDE.md already
covers install, layout, ecc-install-state.json, updating, uninstalling, and the
hook scope decision, so QWEN.md now points there instead of duplicating it.
Counts in the Overview (68 agents / 293 skills / 94 commands) are verified
against the repo and match the figure GEMINI.md already states.
Three command files declared argument-hint with an unquoted flow sequence, so a
strict YAML parse yields an array instead of a string. Harnesses that validate
the field reject the whole file: Qwen Code drops the command with
'frontmatter: Expected string, received array' and the slash command silently
disappears from the catalog.
Quote all three to match the convention already used by plan-canvas.md, pr.md,
prp-pr.md, plan-prd.md and the other argument-hint declarations in commands/.
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.
Phase 1 pipes the build into `tail -20`. Without pipefail the pipeline's
exit status is tail's, so a failed build exits 0. Phase 2 already sets
pipefail, but only after Phase 1 has run, and each phase is its own code
block. Set it at the top of the Phase 1, 3 and 4 blocks, which pipe a gate
into head or tail. Mirrored in pi/core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/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