Review caught this on the note added last commit, and it was right again. `sync`
prints the card's path relative to the repository root; a Markdown image
resolves relative to the file it sits in. Telling the agent to reuse the
printed path is correct only for a README at the root.
Verified: with output `docs/usage.svg`, sync prints `./docs/usage.svg`. Pasted
into `docs/zh-CN/README.md` that resolves to `docs/zh-CN/docs/usage.svg` — the
card exists, the image is broken. The nested README needs `../usage.svg`.
The note now says to rewrite the path for the target file and gives both forms.
Review caught this on the previous commit, and it was right. `sync` resolves
its target as `--out` if given, otherwise `.tokenchit.json`'s `output` —
`tokenchit.svg` is that field's default, not a fixed location. Saying the bare
command "writes tokenchit.svg in the repository root" is wrong for anyone who
has configured another path.
Checked rather than assumed: a config with `output: docs/custom-card.svg` makes
the bare command report `would write docs/custom-card.svg`.
Three places said it. Step 3 now says the configured output, the flag table no
longer implies the default is fixed, and the README snippet carries a note to
use the path sync actually reported. The command's usage comment says the same.
The workflow's step 3 was an executable block containing placeholders:
npx -y @tokenchit/cli@0.10.0 sync [--out PATH] [--theme auto|light|dark] [--dry-run]
Copied as written, that does not fail. `sync` ignores an unrecognised
positional argument rather than refusing it, and `[--dry-run]` is positional —
so the brackets are discarded and a real sync runs. Verified against the
published 0.10.0: the literal command exits 0 and writes tokenchit.svg, 10,144
bytes, while `--dry-run` spelled properly writes nothing. An agent following
this skill for a preview would have changed the working tree.
Step 3 now shows one bare runnable command and moves the options into a table,
so nothing inside a bash block is a placeholder. Three reviewers flagged this
shape — it was the standing merge-risk note on the PR — and it was still live.
Also states the Node floor. The skill said only "Requires Node.js"; the package
declares engines node >=22, so anyone on 20 met a confusing failure instead of
a clear precondition.
The conflicts were all counter bumps. Upstream moved 286 -> 292 skills while
staying at 94 command shims; this branch adds one of each, so the merged values
are 293 skills and 95 shims, carried across the English, Turkish and Chinese
docs. Upstream's 2.2.2 version is kept.
One line needed care and nearly went wrong: the v2.0.0 changelog entry records
"281 skills, 67 agents, 94 command shims". That 94 is a fact about a past
release, not a live counter, and a blanket substitution had rewritten it. It is
back to 94. The Discord snowflake and the Perl badge colour both contain "94"
too and are untouched.
While here, the pin was stale. It named 0.7.0, three minor versions behind, so
anyone installing through this skill got a tool without Gemini support, the
read-only doctor report, PNG export or the portable ledger. Raised to 0.10.0 —
knowingly, which is what the skill's own note asks for — and the documented
`npm view @tokenchit/cli@0.10.0 dist.integrity dist.tarball` was run to confirm
it resolves.
The agent list was stale for the same reason: Gemini CLI became a real adapter
in 0.9.0 and the skill still advertised three agents. Verified against the
published 0.10.0 tarball rather than from memory — `init --help` and `doctor`
both name Claude Code, Codex, Gemini CLI and OpenCode.
Two overlapping first payOnce calls could both pass the session-cap check
while sessionSpent was still 0, then both increment after the confirm await,
exceeding SESSION_CAP. Reserve MAX_AMOUNT before any await and release it
only when the user declines. Mirrored in ja-JP and zh-CN.
Review follow-ups: serve text/html sibling assets under the artifact sandbox CSP (direct navigation no longer bypasses it); resolve MIME from the request name first with target extension as fallback; make security fixtures cross-platform (no angle brackets in filenames, skip when symlink creation is denied). Residual check-then-read TOCTOU documented as accepted for the loopback-local threat model. Tests 33/33 GREEN, ESLint clean.
Serve artifact pages with a sandbox-only CSP mirroring the viewer iframe, fail-closed realpath confinement for sibling assets, and escape the artifact path in the missing-artifact 404. TDD: 4 RED failures (CSP header, 404 escaping, symlink escape) before the fix, 31/31 GREEN after; markdown/sessions/hook suites and ESLint clean.
render() ran after load()'s ordered failure path and after shownLoad
had moved on. A snapshot the board could not show (a session whose
detectedHarnesses is stored as an object) threw past the ordering, and
an older valid snapshot could no longer take the board. render() now
runs inside that path, and shownLoad moves only once it returns.
Run posted state.query, the query typed last. When an older load
succeeds after a newer one failed, the board shows the older query's
results and recall command while Run acted on the newer query. Each
load now keeps its query, render() records it for the board, and Run
posts that.
The test harness assigns new arrays instead of pushing onto
page.pending, and records requests the same way.
Two cases the finished-load rule got wrong. When the first snapshot was
still loading and a live refresh failed, the first snapshot arrived
late and was dropped, leaving the pane empty. And manual loads, the
Refresh button and a query, reported their failures outside the
ordering, so a late manual failure could cover newer data and an older
success could clear a newer manual failure.
The board now shows the newest data any load brought, and the error
box the outcome of the newest load that has finished. load() reports
its own fetch failures, as a live-refresh failure when the interval
started it and as a plain error otherwise, so every load is ordered the
same way. Data that arrives after a newer failure still shows, and the
failure stays, dated by that data.
Two held refreshes complete in reverse order with different data. The
older response arrives last and must not replace what the newer one
put on the board. It did before the previous commit.
A newer refresh that failed was shown, and then an older one that
succeeded cleared the message, so the board looked live although no
load that started after the failed one had succeeded.
The board now follows the newest load that has finished, with its data
or with its failure. A load that finishes after a newer one leaves the
board alone: its data is not rendered and its failure is not reported.
This also keeps a late response from replacing newer data.
With three refreshes in flight, the second succeeds and the first
fails while the third still runs. The board shows data from a load that
started after the failed one, so the failure stays off it. Comparing
against every load started so far would report it.
The refresh-failure test looked for the current Gregorian year in a time
the page formats with toLocaleString. A locale with another calendar
shows another year: under th_TH the page prints 2569 and the test
failed. The page now runs on a fixed instant, and the test compares
against toLocaleString of that instant.
A failed refresh was dropped whenever any load finished while it ran.
When two interval refreshes overlapped and the older one succeeded, the
newer one's failure was dropped too, so the board looked live although
its latest refresh had failed.
Loads are now numbered as they start. A failure is dropped only when
the board shows data from a load that started after it.
A live refresh that fails after a newer load has already replaced the
board no longer describes what is shown, so it is only reported while
the data it was meant to replace is still on screen. The time of that
data now includes its date, for a pane left open overnight, and it is
kept in its own binding instead of on the shared state object.
The 15-second refresh caught every error with `load().catch(() => {})`,
so a control pane that had lost its server kept showing the last
snapshot as if it were current. A failed refresh now shows the error box
with "Live refresh failed. The data below is from <time>." and the
error, and the next successful refresh clears it, as load() already
clears the box.
The new test runs the served page script in a vm with a stand-in for the
browser APIs it uses, fails a refresh, and checks both transitions.
Fixes#3176
`su postgres -c "echo ok" -c "psql -c 'drop table users'"` runs the last
-c, but only the first was scanned, so the drop went through. Every
-c/--command value is now scanned as a command line of its own.
`su postgres -c "psql -c 'drop table users'"` resolved to `su`, so the
SQL-client check never saw psql. The value of su's -c/--command (or of a
short-option cluster ending in c, as in -lc) is now scanned as a command
line of its own, like the value of sh -c. The scan continues past `--`,
since su hands the arguments after it to the target shell, which runs a
-c there too.
taskset joins the runners: its -c is a flag and the CPU mask is one
positional, so `taskset -c 0 psql ...` resolves to psql.
The default chrome-devtools connector is launched with `npx -y` and
`@latest`, so every session start can silently install whatever version
was most recently published to npm. Pinning makes the default connector
reproducible and removes the unpinned-npx finding that /security-scan
(AgentShield) reports against ECC's own .mcp.json.
The same spec is pinned in the Codex merge script so both harnesses
stay in sync, and the Codex merge test is updated to match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per chenzh01 review: the blanket ECC_TEST_ALLOW_FAILURES=1 env var masks all failures. Replace with ECC_TEST_SKIP listing only the known-broken test files. Any new failure will break CI. Added skip-pattern warning in test output for visibility.
- Review a caller-supplied diff when given; otherwise collect committed
branch, staged, unstaged, and untracked Ruby changes. Plain `git diff`
missed staged-only and committed PR changes.
- Add update_all/delete_all/update_columns/insert_all/upsert_all on
user-reachable scopes to the HIGH tier, since they skip validations
and callbacks.
- Fix stale 67-agent catalog blocks in README.md and docs/ja-JP/README.md
and list ruby-reviewer there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
kimi.com/code serves mainland China (WeChat / +86 sign-in, Alipay and WeChat Pay), so ECC's international readers cannot sign up or pay there.
Point the Kimi Code link in the install-target tables at kimi.ai/code (Google sign-in, card payment) instead, keeping the same aff value.
Sync skills/tinystruct-patterns against tinystruct's own canonical copy of
this skill (.agents/skills/tinystruct-patterns/), which the framework repo
keeps up to date with each release:
- Document the bin/dispatcher / bin/dispatcher.cmd launcher generation
workflow (never hand-write or copy it; the framework generates it via
ApplicationManager.init()).
- Add native MCP server/tool integration (org.tinystruct.mcp.*), including
the prompt-injection warning for tool return values and overloaded-tool
schema merging.
- Add annotation-based DB mapping (@Table/@Id/@Column) alongside XML
mapping, the --mapping annotation generator flag, and table
auto-creation via database.autocreate.
- Add the programmatic logging/diagnostics subsystem (ANSI console colors,
StackWalker-based caller tracing, per-package level overrides).
- Update the architecture package map accordingly.
Also update the Japanese translation (docs/ja-JP/skills/tinystruct-patterns)
to match, which had drifted to an older, condensed version of this skill.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a Ruby and Rails code reviewer agent covering Active Record query
safety, strong parameters, unsafe reflection and deserialization, XSS,
safe migrations, and RSpec/Minitest quality. It builds on the existing
rules/ruby/* and rails-patterns skill.
- Register it in .opencode/opencode.json with a matching prompt file
- Map ruby and rails to it in the orch-review workflow
- Bump the documented agent count from 68 to 69 across README, AGENTS.md,
plugin manifests, and translated docs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GNU env -S splits only its own argument; the ones after it reach the command unchanged. Joining them into the split string let a quoted ';' inside a later argument read as a command separator, so env -S 'echo' '; psql -c "drop table users"' was denied. Each later argument is now escaped back into a single literal word, which still leaves env -S 'psql -c' 'drop table users' denied.
#3107 resolves sudo, doas and env before matching a SQL client, and
recurses into sh -c. Six shapes still reached a client unseen and were
allowed:
bash -lc "psql -c 'drop table users'" a -c inside a flag cluster
env -S 'psql -c "drop table users"' the command inside one argument
env --split-string='...' (same)
env time -f "%E" psql -c "..." time, nice, timeout, nohup,
nice -n 10 psql -c "..." stdbuf, ionice, setsid,
timeout 30 psql -c "..." command, exec as runners
Unwrap those runners with their options and positionals, stacked without
a fixed limit, scan env -S payloads as command lines, and accept a -c
inside a short-option cluster. Past the shell recursion limit the check
now fails closed: five levels of sh -c used to hide a drop table.
#3107fixed#3024 on main with its own SQL-client detection, so this
branch restarts from main's code. The wrapper shapes #3107 does not
unwrap follow as a separate commit.