Addresses review feedback on #3277: the previous commit sandboxed the
primary config path, but Config::global_config_paths() also reads the
legacy $HOME/.claude/ecc2.toml. A test that called Config::load() would
pick up (or fail on a malformed) developer-local legacy config.
Under cfg(test), legacy_global_config_path() now resolves inside the same
per-test-thread scratch root as config_path().
Proof of red (new test, before the fix):
$ cargo test legacy_config_path_under_test
running 1 test
test config::tests::legacy_config_path_under_test_is_sandboxed_away_from_user_home ... FAILED
failures:
---- config::tests::legacy_config_path_under_test_is_sandboxed_away_from_user_home stdout ----
tests must never read the real legacy user config, got /Users/<me>/.claude/ecc2.toml
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
config::tests::legacy_config_path_under_test_is_sandboxed_away_from_user_home
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 491 filtered out; finished in 0.00s
error: test failed, to rerun pass `--bin ecc-tui`
After the fix:
$ cargo test legacy_config_path_under_test
test config::tests::legacy_config_path_under_test_is_sandboxed_away_from_user_home ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 491 filtered out
$ cargo test # full suite
test result: ok. 491 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
Refs #3276
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Y54kLgLGzLGqAxwLMFEc9
Dashboard tests that spawn sessions reach Config::save() through
auto_split_layout_after_spawn, which wrote to the developer's real
~/Library/Application Support/ecc2/config.toml. The fixture's db_path
pointed at a deleted temp dir, so the next `cargo run` failed with
"unable to open database file".
Under cfg(test), config_root() now returns a per-test-thread scratch
directory that is removed when the thread exits, so no test can reach
the real user config.
Fixes#3276
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Y54kLgLGzLGqAxwLMFEc9
`package.json` `files[]` enumerates each top-level `scripts/*.js` by hand, and
`scripts/github-coordination.js` was never added. npm therefore drops it from
the tarball while `scripts/lib/` still ships wholesale, so only the CLI entry
disappears.
All seven `/epic-*` commands ship in `commands/` and each instructs the agent
to run:
node scripts/github-coordination.js <command> <issue> --repo <owner/repo>
so anyone who installs from npm -- including the documented
`npm i -g ecc-universal` path -- gets seven commands that cannot run:
Error: Cannot find module '.../ecc-universal/scripts/github-coordination.js'
It works from a git clone, which is what masked it locally.
Add the missing `files[]` entry, the matching publish-surface allowlist entry in
`tests/scripts/npm-publish-surface.test.js` (CONTRIBUTING asks for both), and a
guard test that fails when any published command invokes a `scripts/*.js` that
the publish surface does not cover. The guard reproduces this exact bug: with
`package.json` reverted it fails naming only `github-coordination.js`.
Refs #3270
OllamaProvider built the /api/chat payload without the tools from
LLMInput, so Ollama models were never told any tools existed and a
ReActAgent backed by Ollama never ran one. The response side already
parsed message.tool_calls.
Send the tools in the OpenAI function format that /api/chat accepts,
matching the other providers.
Fixes#3274
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
Self-hosted reasoning models served through vLLM or llama.cpp without a
reasoning parser, or through Ollama without a thinking template, return
their chain of thought inside message content. The OpenAI-compatible
providers and the Ollama provider passed it through verbatim, so
LLMOutput.content and ReActAgent final answers began with the reasoning.
Models whose chat template prefills <think> in the generation prompt
(Spark-X2.5, Qwen3 thinking variants) only emit the closing tag, so the
content arrives as "...</think>answer" with no opening tag.
Add strip_reasoning() and apply it in the OpenAI, Astraflow, Atlas and
Ollama providers. A <think> that does not open the output is kept as a
literal, and unterminated reasoning is left unchanged.
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
The secure-storage example authorized `download` against a raw path string
taken from the request and generated the temporary URL before the check.
A string argument never resolves to a policy, and authorizing a
client-supplied path teaches an IDOR-prone pattern.
Bind a model (Document), authorize against it first, then sign the URL for
the stored path on the s3 disk configured just below. pi/core regenerated
via scripts/build-pi-core.js; skill-triggers registryDigest rebound.
Second upstream sync this cycle: main c70874fa (#3264, curated pi/core
skills + 2.2.2 release sync) touches manifests/context-packs/skill-triggers@1.json,
the same field this branch bumps.
Resolution: main's manifest (293 canonical skills, triggers 32) with
registryDigest recomputed over the merged tree via the repo's own chain
(scripts/lib/context-pack-registry.js -> digestObject). Algorithm
validated first: the same computation reproduces main's committed digest
on main's tree and the prior head's committed digest on that tree exactly.
Verification: node scripts/ci/validate-context-profiles.js (293 skills,
triggers 32/293) OK; plan-orchestrate registry fixtures pass; full
node tests/run-all.js = 704 passed / 0 failed.
getDefaultClaudeAgentDataHome ignored CLAUDE_CONFIG_DIR and always returned ~/.claude, so sessions with a relocated Claude config dir wrote ECC data to the wrong place when ECC_AGENT_DATA_HOME was unset. Fixes#3266.