Lockfile-only Dependabot Cargo group update for ecc2. Verified PR was clean/mergeable with no failing or pending checks and no unresolved review threads.
Add a local-first, cross-harness memory vault with CLI and MCP surfaces, bounded search and storage, harness-scoped visibility, setup guidance, and comprehensive tests.
Map the ECC Ultra report into ten dependency-ordered execution lanes with explicit security, consent, schema, lifecycle, testing, and distribution gates.
Add a contract-first workflow for consumer/provider collaboration, including shared artifact authority, compatibility review, generated-type and runtime verification, and safe handling of contract-driven tooling.
Honor an explicit non-git CLAUDE_PROJECT_DIR as an isolated project scope, canonicalize and hash it consistently in the shell observer and Python CLI, and preserve the global fallback for arbitrary non-git working directories.
Preserve complete Stop-hook stdout through lifecycle wrappers, wait for queued output to flush before exiting, bound child output with a larger explicit buffer, and add end-to-end regressions for large, multibyte, dry-run, and failure cases.
* fix(resolve-ecc-root): require ECC skills, not just scripts, before accepting a root (#2544)
resolveEccRoot() accepted a candidate root on script-only evidence
(scripts/lib/utils.js). A partial install that lands ECC's scripts into
~/.claude but not ECC's skills short-circuited at the standard-install
branch, so skill-resolving callers built skills/... paths against a root
where they do not exist and every command failed three layers away.
For the default probe (skill consumers, reached via INLINE_RESOLVE) a
candidate now qualifies only if it contains both the script tree and a
sentinel ECC skill; the same stricter check guards the plugin-root and
plugin-cache branches. An explicit caller probe is still honored exactly,
so script consumers (e.g. session-start-bootstrap, which probes for the
hook runner) are unaffected. Merely checking that skills/ exists is
insufficient — a user's own ~/.claude/skills/ can be present with none of
ECC's skills.
Adds a regression test for the exact partial-install scenario and updates
the resolver test fixtures to build complete roots.
* test(resolve-ecc-root): cover partial exact-plugin and cache roots; DRY skill sentinel (#2544)
Address CodeRabbit review on PR #2577:
- Extend #2544 regression coverage to the exact-plugin and versioned
plugin-cache branches, asserting the stricter both-sentinels predicate
rejects a scripts-only root there too (not only for ~/.claude).
- Extract the ECC_SKILL_SENTINEL constant in command-plugin-root.test.js
and reuse it at both fixture setup sites instead of duplicating the literal.
Replace the generated Itō SVG wordmark with the supplied transparent monogram assets, keep the exact white-and-gold mark for dark mode, add a same-geometry light-mode variant, and refresh the dependency lock entry flagged by CI.
Merge the official ECC Tools favicon mark, normalized Itō Drive lockup canvas, and equal 16:9 guide cards after full CI and GitHub-render visual verification.
Expose the canonical Itō CLI's pinned sixtytwo node-qualification path through ECC with double opt-in, explicit node/config gates, credential isolation, and no new MCP or execution authority.
Validated across the full Linux, macOS, and Windows Node/package-manager matrix, hosted coverage, CodeQL, security, lint, and focused bridge tests.
* docs: add Itô Markets GPU compute sponsor section to README
Adds a GPU compute sponsor entry under the Sponsors section: Itô Markets
logo linking itomarkets.com plus the compute dashboard at
compute.itomarkets.com, matching the framing from the merged sponsor
routing (4e341183, 8eb43383). Includes a hidden HTML-comment placeholder
for Moonshot AI (Kimi), pending countersign; nothing renders publicly
for Moonshot yet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149VwNuynam6rvEfcMmiHHa
* docs(sponsors): consistent sponsor treatment, Moonshot public, real brand assets
- Replace cropped ito.svg with the full Drive lockup; add ito-dark.svg
(white marks) with picture/source theme switching for dark mode
- Add Moonshot AI (Kimi) as a public business sponsor: logo in the top
sponsors table, SPONSORS.md row, placeholder comment removed
- Swap atlascloud.png for the official atlascloud.svg wordmark; add
dark-theme variants for Atlas Cloud and Moonshot
- Rewrite the bottom Sponsors section: one consistent list, no
Ito-only subsection or stray horizontal rule
- Point the self-host/gateway FAQ at the ito-compute skill for
fixed-rate GPU blocks (npm CLI marked coming soon, not yet published)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149VwNuynam6rvEfcMmiHHa
* docs: drop em dash from sponsor FAQ line
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149VwNuynam6rvEfcMmiHHa
* docs(sponsors): Itô is a Partner, Moonshot AI (Kimi) are Open Source Friends
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149VwNuynam6rvEfcMmiHHa
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The existing OpenAI pattern sk-[a-zA-Z0-9]{20,} never matches real
Anthropic keys: their sk-ant-api03-... format contains hyphens, which
break the character class before reaching the 20-char threshold. Keys
from the fastest-growing Claude Code user base slipped through the scan.
Adds a dedicated sk-ant-[a-zA-Z0-9_-]{20,} pattern (checked before the
OpenAI one) and extends the staged-secrets test with a realistic
Anthropic key fixture.
Two continuous-learning-v2 observer regressions reported in #2452:
- start-observer.sh still called _ecc_resolve_homunculus_dir, but the
shared lib was renamed to _clv2_resolve_homunculus_dir (with observe.sh
and detect-project.sh updated, start-observer.sh missed). Under set -e
every launch dies with exit 127 at line 40 - daemon boot is broken on
all platforms, not just Windows.
- observer-loop.sh backgrounds the analysis claude call with stdin left
open; on Git Bash/MSYS2 the child inherits it, waits, warns 'no stdin
data received', and exits 1 before reading the analysis file. Close
stdin with </dev/null while keeping the -p prompt flag, preserving the
Windows-compat decision from #842 instead of reverting to a stdin
redirect.
Adds two source invariant guards to tests/hooks/hooks.test.js: every
*_resolve_homunculus_dir call site must match a function the shared lib
defines, and the backgrounded claude call must close stdin.
Fixes#2452
* fix(suggest-compact): recognize large-window model families without a [1m] marker
resolveContextWindowTokens() only detected a 1M window via the env
override, the [1m] model-id marker, or observed tokens already above
200k. Large-window models whose ids carry none of these (e.g.
claude-fable-5) were misclassified as 200k windows, overstating
context usage ~5x in the compact suggestion.
Add a known-model-family substring table (claude-fable-5,
claude-mythos-5) checked after the env override and [1m] marker and
before the token-count heuristic. Env overrides still win, and unknown
model ids still fall back to the 200k default.
Closes#2461
* fix(suggest-compact): anchor known-model-family match at a token boundary
Unanchored substring matching would misclassify a hypothetical smaller
tier sharing a known family prefix (e.g. claude-fable-5-mini) as a 1M
window. Require the family id to end at a token boundary: end of id, a
delimiter, or a dated/versioned suffix (-20260115). Alphanumeric
continuations and letter suffixes no longer match.
Addresses CodeRabbit/Greptile review on #2468
On a case-insensitive filesystem (macOS APFS/HFS+, Windows NTFS) a write to
`.ESLINTRC.JS` lands on the exact same inode as `.eslintrc.js`, but the guard
looked the basename up in PROTECTED_FILES with a case-sensitive `Set.has`.
Every entry in that Set is lowercase, so any case-variant path missed the
branch entirely and returned exit 0 — a single Write silently overwrote a
live config while the hook reported success.
Reproduced on macOS APFS: `.eslintrc.js` and `.ESLINTRC.JS` share one inode,
yet the hook returned exit 2 for the former and exit 0 for the latter, and the
uppercase write replaced the real config's contents.
This is a one-step bypass of the whole guard and needs no shell access, unlike
the known delete-then-recreate route.
Fix: also test `basename.toLowerCase()`. All 32 PROTECTED_FILES entries are
already lowercase, so the fallback is exact. On a genuinely case-sensitive
filesystem this costs at most a false positive on a distinct file whose name
differs from a protected one by case alone.
Behaviour deliberately unchanged: first-time creation is still allowed (the
bootstrap affordance), non-config paths still pass through, and the existing
lstat/ENOENT fail-closed semantics are untouched.
Test: adds a case-variant case that asserts exit 2. It guards itself with an
inode comparison and skips on case-sensitive filesystems rather than asserting
something untrue there. Verified in both directions — it FAILS against the
unpatched hook (`Got 0; 0 !== 2`) and passes with the fix. Suite: 9/9.
* fix(hooks): remove stray '?' that made every 'yarn <anything>' trigger tmux reminder
The tmux-reminder matcher uses one alternation per package manager. Each
branch requires a subcommand (install|test) — except yarn, whose subcommand
group carried a trailing `?`:
yarn (install|test)?
That made the subcommand optional, so the branch degraded to "yarn " plus
anything: `yarn add foo`, `yarn build`, `yarn dev`, even `yarn --version`
all matched and spammed the "Consider running in tmux" hint into the
additional-context channel.
Drop the `?` so yarn matches parity with npm/pnpm/bun. Verified locally
against 14 cases (yarn install/test still fire; yarn add/build/dev/… no
longer do; npm/pnpm/bun/pytest behavior unchanged).
Fixes#2514
* test(hooks): add pre-bash-tmux-reminder regression tests
Add coverage for the tmux-reminder matcher following the auto-tmux-dev.test.js
structure — the regex-first hook now has direct regression tests for the yarn
branch fix in this PR (and for the sibling package managers, other matched
tools, TMUX bypass, and malformed input).
16 assertions total:
- fires for: yarn install, yarn test, npm install, pnpm test, bun install,
pytest tests/, cargo build
- does NOT fire for: yarn add react, yarn build, yarn dev, yarn --version,
bare `yarn`, npm run dev
- respects TMUX env var
- tolerates invalid JSON and missing command field
Verified the tests actually catch the bug: reintroducing the buggy
`yarn (install|test)?` fails 4 of the 5 yarn non-match cases (the fifth,
bare `yarn`, stays passing because even the buggy branch requires a trailing
space after yarn).
Addresses CodeRabbit review on #2517.
* test(hooks): fail loudly on spawn errors, use destructuring, split runTests
Address three CodeRabbit review notes on tests/hooks/pre-bash-tmux-reminder.test.js:
- Fail loudly on spawnSync errors: raise instead of coercing
`result.status || 0`, which would mask spawn errors, timeouts, or signal
termination as a successful exit 0 (masks legitimate test failures).
- Use destructuring (`const { TMUX, ...env } = process.env`) instead of
copy-then-`delete` so the base env is built immutably.
- Split `runTests` (was 66 lines) into small per-group helpers
(runYarnTests, runSiblingPackageManagerTests, runOtherToolTests,
runTmuxBypassTests, runEdgeCaseTests). `runTests` is now 18 lines and
purely orchestrates.
16 assertions still pass; no coverage changes.
The 4th CodeRabbit note (avoid console.log in test files) is intentionally
not adopted here — every sibling hook test in this repo
(auto-tmux-dev.test.js, bash-hook-dispatcher.test.js, block-no-verify.test.js,
etc.) writes to console.log because the project's own test runner
(tests/run-all.js) is console-log based and there is no Jest/Mocha
dependency. Diverging from the established convention in a bugfix PR is
out of scope.
* test(hooks): trim tmux reminder regression coverage
---------
Co-authored-by: Haley Chen <2022hachen@gmail.com>
Commit 2d40baac (PR #2304) renamed `_ecc_*` -> `_clv2_*` but missed this
single call site. The launcher sources `scripts/lib/homunculus-dir.sh` which
only exports `_clv2_resolve_homunculus_dir`, so any user enabling the
observer (`observer.enabled: true`) gets:
start-observer.sh: line 40: _ecc_resolve_homunculus_dir: command not found
The hook (`observe.sh`) uses the correct name and writes observations, but
the lazy-start path fails silently via nohup, so the symptom is
"observations grow forever, no new instincts". Confirmed on
affaan-m/ECC@40927950c (HEAD of main).
Default `observer.enabled: false` masks the bug for new users. Opt-in
users hit it on first manual `start-observer.sh start` or first lazy-start
after enabling.
Fix: rename the single call to `_clv2_resolve_homunculus_dir` to match
the lib export and every other caller in the skill.
The 'Parallel Task Execution' rule encourages agents to spawn subagents,
but defines no completion contract. Observed failure mode: subagents
followed the rule, spawned their own children, and returned 'waiting
for background agents' as their final answer. All children completed
successfully, but their results were orphaned because a parent whose
turn has ended cannot receive completion notifications - leaving
zombie 'running' tasks and lost work.
Adds three rules that apply at every delegation depth:
1. Your final message IS the deliverable (never end with 'waiting')
2. If you delegate, you own collection (no fire-and-forget)
3. Decompose only when work cannot fit in one context
* refactor(hooks): consolidate PostToolUse hooks into sync/async dispatchers
Replace 10 individual PostToolUse entries in hooks.json with two
consolidated dispatcher entries (post:dispatcher:sync /
post:dispatcher:async). The dispatcher's internal registry preserves
every hook ID, matcher, and profile, so ECC_DISABLED_HOOKS and
ECC_HOOK_PROFILE gating behave exactly as before.
Performance (Edit event, actual hooks.json commands spawned in
parallel like the harness does, median of 7 runs):
- Blocking hook latency: 81ms -> 49ms (~40% faster; 7 blocking
processes -> 1 sync dispatcher)
- Node processes per tool call: 10 -> 2 (7 blocking + 3 async
-> 1 sync + 1 async)
- observe-runner now runs in-process (~370ms) inside the async
dispatcher, which stays backgrounded (async: true, timeout 45s),
so it adds no user-facing latency.
Also:
- dashboard-web lists dispatcher-managed child hooks so the hook
inventory stays complete
- post-edit-console-warn refactored to export run() for in-process
dispatch while keeping standalone stdin behavior
- dispatcher stdin reading is multi-byte safe (StringDecoder) and
child hook exit codes propagate to the dispatcher exit code
* test(hooks): replace emoji literal with unicode escape for CI unicode safety check
* fix(hooks): adopt explicit cli() entrypoint and merge multi-hook stdout
Address Greptile review on #2494:
- Replace the non-standard 'require.main === undefined' guard with an
explicit exported cli(). The hooks.json bootstraps now call
require(s).cli(), so merely requiring the module (dashboard-web,
test runners, Jest, worker threads) can never trigger dispatch,
attach stdin listeners, or set process.exitCode.
- Replace last-writer-wins stdout with mergeHookStdout(): when several
hooks emit additionalContext envelopes they merge into a single
PostToolUse envelope; non-mergeable raw stdout keeps the last hook's
output and emits a stderr warning naming the dropped hook IDs, so
nothing is lost silently.
Also includes local formatter reformatting of the dispatcher and its
test file (no behavioral changes beyond the above).
* fix(hooks): keep post:bash:dispatcher phase reachable in minimal profile
The Greptile P1 premise was partially incorrect: sub-hooks without
explicit profiles default to standard,strict via parseProfiles()
(scripts/lib/hook-flags.js), so audit/cost logs never ran under the
minimal profile on main either — there is no user-visible regression.
However, main did spawn the bash dispatcher phase unconditionally and
let each sub-hook gate itself. Restore that semantic by opening the
outer registry gate to minimal,standard,strict so a future sub-hook
that opts into minimal is not silently blocked at the phase level.
Adds the previously missing minimal-profile async dry-run test.
* test(hooks): assert failing hook exit code propagates to real process status
Spawns the actual dispatcher subprocess with an injected failing hook
and asserts the OS-level exit status, stderr diagnostic, and suppressed
pass-through — closing the E2E gap CodeRabbit flagged on #2494.
* chore: retrigger CI (flaky windows powershell bootstrap test)
Bun switched its default lockfile from the binary bun.lockb to the
text-based bun.lock, but detectFromLockFile() only ever checked for
bun.lockb — a project using modern Bun would never be detected as
using Bun at all.
Added bun.lock as the primary lockfile with bun.lockb kept as a
recognized legacy alias, so either format is detected correctly.
Also ignore stray bun.lock/bun.lockb at the repo root: yarn is this
repo's canonical package manager (package.json "packageManager"), so
a lockfile from someone running `bun install` locally shouldn't get
picked up by git status.
* fix: make the installer runtime pass strict supply-chain vetting
Remediate the four enterprise supply-chain vetting blockers from
affaan-m/ECC#2502 so the installer runtime (package.json + manifests +
scripts/lib/**) passes strict exact-pin evidence policy:
1. Remove the package.json `postinstall` lifecycle script (it only echoed a
post-install banner) and move that banner to an explicit opt-in
`npm run welcome` command. No install-time lifecycle script remains.
2. Exact-pin every dependency in package.json (dependencies + devDependencies)
to the versions already resolved in package-lock.json; no ^/~ ranges.
3. Replace non-ASCII characters on the installer runtime script/config surface:
em-dashes (U+2014) in scripts/lib/{path-safety,install-executor,
install/link-rewrite}.js comments and the two "Itô" (U+00F4) occurrences in
manifests/{install-components,install-modules}.json descriptions become
ASCII, so strict-surface Unicode scanners are clean.
4. Drop the bare `require("ajv")` from scripts/lib/install-state.js; the file
already carries a complete hand-rolled validator enforcing the same
schemas/install-state.schema.json (ecc.install.v1) constraints, so the
installer closure is dependency-free (zero non-builtin bare requires).
Refs affaan-m/ECC#2502
* fix: avoid unpinned welcome invocations
Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>
* fix: validate translated skill frontmatter
Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>
* fix: repair skill frontmatter YAML
Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>
* fix: add MIT license to core skill manifests; pin verification-loop tsc invocation
* fix: preserve tsc/pyright exit status in verification-loop type-check (set -o pipefail)
* chore(deps): sync lockfiles with exact-pinned package.json
Regenerate package-lock.json and yarn.lock so the pinned dependency
specs are reflected in both lockfiles. npm ci and Yarn's --immutable
install now pass the sync check. The resolution tree is unchanged
(231 yarn resolutions, byte-identical set; zero npm transitive drift);
only the root descriptor strings move from ranges to the versions
already resolved in the committed lockfiles.
Addresses the Codex P1 on #2503.
---------
Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>
Co-authored-by: Samarjeet Singh Tomar <samartomar@gmail.com>
* fix(project-detect): parse Python deps pinned with ~= and @ direct references
* test(project-detect): cover ~= compatible-release and @ direct-reference parsing
* test(project-detect): cover direct references
* fix(project-detect): skip bare VCS/URL requirement lines in getPythonDeps
A requirements.txt line like git+https://host/repo.git#egg=pkg carries
no leading package name, so the delimiter split recorded the whole URL
fragment as a dependency. Skip names that start with git+ or contain a
URL scheme, and tighten the test assertions so any leaked URL, scheme,
or @ delimiter fails loudly.
* fix(plan-canvas): stop dropping list items when a block's first item is over-indented
* fix: address greptile findings for PR #2501 - list-type detection and outdent nesting
- plan-canvas markdown: fix nested list rendering where outdented runs (indent
6→4) create duplicate sibling UL blocks instead of sharing parent (#2501)
- transcript-context: add LARGE_WINDOW_NATIVE_MODEL_IDS array for models whose
default context window is 1M but do NOT carry the [1m] marker (fixes#2497)
- Add test coverage for transcript-context and shell-substitution modules
- Add test coverage for project-detect module (#2498)
* fix(plan-canvas): handle outdented list runs
* fix(plan-canvas): start a new list when marker type changes at the same indent
CommonMark treats a marker-type change (bullet to ordered or back) at
the same indentation as the start of a new list. buildList previously
absorbed the run into the current list, so mixed runs rendered under a
single wrong tag. Stop the run on a tag change and let buildListBlock
render the next run as a sibling list with its own tag.
* fix(llm/providers/claude): attach cache_control to system block, not top-level
The Anthropic Messages API does not accept `cache_control` as a top-level
request parameter — it is a per-content-block field. Passing it at the top
level raises `TypeError` in the Python SDK (which validates kwargs against
`messages.create()`'s signature) or a `400 unknown_parameter` from the API,
so every ClaudeProvider.generate() call fails.
Move `cache_control: {"type": "ephemeral"}` onto the last system-prompt
block so ephemeral prompt caching still works when a system prompt is
present, and drop it when there isn't one (nothing to cache).
Existing tests didn't catch this because `FakeMessages.create(**_params)`
accepted anything and ignored the kwargs. FakeMessages now records
`last_params`, and two regression tests assert that:
- `cache_control` never appears as a top-level param, and
- when a system prompt is set, `cache_control` rides on the last block.
Fixes#2512
* test(claude_provider): split composite isinstance+truthiness assertion (PT018)
Ruff PT018 flagged the combined `isinstance(system, list) and system` check
in `test_generate_does_not_pass_cache_control_as_top_level_param`. Split
it into two focused asserts (`isinstance(system, list)` then `assert system`)
so a failure points at the exact violation instead of a compound condition.
Behavior unchanged; 6/6 tests still pass.
Addresses CodeRabbit review on #2515.