Use no-follow file descriptors for legacy Codex snapshots, verification, restoration, and marker cleanup. Quarantine candidate removals and verify inode identity before deletion.
Carry the lifecycle runner as a verified artifact so privileged release workflows never dynamically check out and execute an output-selected revision.
* feat: add thin Pi adapter mounting ECC's canonical skills and commands
Adds first-class Pi (@earendil-works/pi-coding-agent) support as a thin
adapter layer, following the maintainer review on #2352. ECC's canonical
assets stay the single source of truth: nothing is copied or generated
under .pi/.
The `pi` manifest in package.json points Pi directly at `skills/` and
`commands/`. No transformation is needed — ECC's SKILL.md files already
follow the Agent Skills standard Pi implements, and ECC's command
frontmatter is already Pi's prompt-template format.
.pi/extensions/index.ts is the only adapter logic. It:
- uses Pi's documented `pi.on(...)` lifecycle, not an undocumented event bus
- resolves hook scripts from the installed package via `__dirname`, never
`process.cwd()`, so global installs work from any project directory
- runs hooks with `execFile(process.execPath, [...])` and no shell, so paths
containing spaces or shell metacharacters are safe
- invokes hooks through ECC's own `run-with-flags.js`, so `ECC_HOOK_PROFILE`
and `ECC_DISABLED_HOOKS` keep gating hooks under Pi
- runs hooks in the user's project directory so project detection stays
correct, while resolving the scripts themselves package-relative
- injects the SessionStart hook's `additionalContext` into the system prompt
on the next `before_agent_start`
- isolates hook failures behind a timeout and an output limit
- registers `/ecc-doctor` for install diagnostics
Registers `.pi` in the platform-configs install module and adds a Pi row to
the harness adapter compliance matrix.
Verified against Pi 0.84.1: a global `pi install` exposes 285 skills and 94
commands resolved from `skills/` and `commands/`, plus `/ecc-doctor`, with
no generated copies.
Scope deliberately excludes subagents, chains, approval gates, todos,
profiles, and MCP; ECC works in Pi without any companion package.
* fix: address review findings on the Pi adapter
Bot review on #2759 surfaced two real runtime defects and several
hardening gaps.
Runtime fixes:
- Attach an `error` listener to the hook child's stdin. `stdin.end()`
writes asynchronously, so a hook that exits, short-circuits, or is
killed by the timeout before reading the payload raises EPIPE as an
`error` event that the surrounding try/catch cannot see. Unhandled,
that event would terminate the Pi session and break the isolation
guarantee the adapter documents.
- Clear `pendingContext` at the top of the `session_start` handler. Pi
can start a new session (/new, /resume, /fork) before
`before_agent_start` consumes the previous value; if the newer hook
then failed, the next agent start received context describing a
different session's project state.
- Replace `require.resolve` companion detection with a read of Pi's own
`packages` list, honoring `PI_CODING_AGENT_DIR`. Pi installs packages
under its config directory, which is not on Node's module resolution
path from the extension, so the previous check reported every
companion as missing no matter what was installed.
Compliance matrix: remove internal semicolons and a trailing period from
the Pi record's list entries. The renderer joins entries with "; ", so
those characters split one entry into several in the rendered cell.
Tests: run profile gating against the temp skeleton instead of the real
checkout so it cannot leave marker artifacts behind; count files under
.pi/ by walking disk rather than git, so untracked copies cannot bypass
the regression guard; allow negated phrasing in the README heuristic;
pin the adapter's real parser guards with source assertions so the local
mirrors cannot silently diverge; add coverage for EPIPE isolation, stale
context clearing, and companion detection.
* docs: point users at existing companion Pi packages instead of bundling them
Every capability listed as out of scope is already provided by a maintained
community Pi package: pi-subagents, @juicesharp/rpiv-ask-user-question,
@juicesharp/rpiv-todo, and pi-mcp-adapter for MCP.
Pi supports pulling other pi packages in via dependencies plus
bundledDependencies, but this adapter deliberately does not. Bundling would
ship third-party code that executes with full user permissions in every ECC
install, turn optional capabilities into mandatory ones, and add four
fast-moving pins to maintain.
Instead /ecc-doctor now prints the exact `pi install npm:<name>` command for
each companion it does not find, so adopting one stays a deliberate user
choice.
Also corrects the MCP claim: Pi core has no MCP surface by design, but the
community pi-mcp-adapter package adds one. This adapter neither installs nor
verifies it, and ECC's MCP reference configs are not known to be compatible.
* docs: ECC's MCP configs work in Pi through pi-mcp-adapter, verbatim
Tested rather than assumed. The community pi-mcp-adapter package reads the
standard mcpServers format from .mcp.json and ~/.config/mcp/mcp.json, which
is exactly the format ECC already uses in .mcp.json and
mcp-configs/mcp-servers.json.
Verified against pi-mcp-adapter 2.21.2 in an isolated PI_CODING_AGENT_DIR:
copying mcp-configs/mcp-servers.json to a project's .mcp.json registers Pi's
`mcp` tool and `/mcp` command with all 35 ECC servers discovered, coexisting
with this adapter's /ecc-doctor. No translation layer and no ECC change are
needed, so this stops being a limitation and becomes documentation.
Recorded caveats: the adapter's first run against a new config performs
initialization that blocks in non-interactive mode, and only discovery was
verified, not live tool invocation.
ECC still neither installs nor depends on the package.
* feat: inject ECC's canonical engineering rules into Pi's system prompt
ECC's rules were the one durable asset the adapter did not deliver: skills
and commands reached Pi in full, but the 122 rule files that carry ECC's
coding style, testing, security, git workflow, and code-review standards
did not, so ECC in Pi was a library of skills rather than a set of
enforced standards.
Rules are read at runtime from the canonical rules/common/ directory of
the installed package and appended to the system prompt inside an
<ecc-engineering-rules> block. Nothing is copied or generated under .pi/,
which keeps the single-source-of-truth constraint this PR exists to
satisfy. Injection reuses the before_agent_start path already built for
session context, so no new lifecycle mapping is introduced.
Rules are re-applied every turn because they are standing policy, while
the session context stays one-shot and is consumed on first use.
agents.md, hooks.md, and performance.md are excluded: they describe Claude
Code primitives Pi does not have (Task/TodoWrite delegation, Claude hook
event types, thinking-budget toggles), so injecting them would point the
model at tools that are not there. A test asserts they stay excluded, and
a leakage test asserts none of those primitives appear in the injected
text. Language-specific rules under rules/<language>/ are out of scope for
this first adapter.
Injection is bounded by MAX_RULES_BYTES and can be disabled with
ECC_PI_RULES, following ECC's existing off-switch convention. /ecc-doctor
reports the state and injected size.
Measured on this repo: 7 files, 12,361 characters, roughly 3k tokens.
Also replaces a Function() call in the test helper with direct arithmetic,
and repins a stale assertion that pinned one spelling of the context
handoff rather than the guarantee (read before clear, clear before return).
* fix: /ecc-doctor misreported filtered packages and partial rule installs
Two reporting defects in /ecc-doctor, the command whose whole job is telling
a user what is actually installed.
Pi's settings accept a `packages` entry in two shapes: the bare source string
("npm:pi-subagents") and an object carrying that source alongside resource
filters ({ source: "npm:pi-subagents", skills: [] }). normalizePiPackageName
only recognized the string, so a user who narrowed which resources a companion
contributes was told the companion was not installed, along with an install
command for something already present. The source type still decides whether a
name is comparable, so an object wrapping a git source or a path stays
unrecognized exactly as before.
loadPortableRules drops rule files it cannot read, drops empty ones, and stops
at MAX_RULES_BYTES, but describeRulesStatus reported PORTABLE_RULE_FILES.length
regardless. A partial install that loaded 3 of 7 files reported "7 rule file(s)"
to the one command a user runs to find a partial install. The loaded count is
now tracked next to the cache and reported as a ratio, with the shortfall named.
Also reconciles the Notes bullet in .pi/README.md, which still called MCP out of
scope after the MCP section landed documenting that ECC's configs load in Pi
through pi-mcp-adapter.
Both defects were reported by CodeRabbit and verified against Pi's own
packages.md before fixing. Adapter tests go from 24 to 26; the two source
contracts that pinned the previous spellings now pin the new guards, so the
object-form unwrapping and the loaded-count reporting cannot be silently
reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(continuous-learning-v2): warn when the observer never survives a hook invocation (#2489)
The observer is lazy-started from a hook process that exits immediately
afterwards. start-observer.sh's liveness check runs inside that still-living
process tree, so it always sees a healthy observer and prints "Observer
started (PID: N)". On native Windows (Git Bash/MSYS2) the reap happens later,
when the hook's Job Object closes, so no self-check placed in
start-observer.sh can ever observe the failure.
The next hook invocation is the only place the death is visible, and
_CHECK_OBSERVER_RUNNING already found it there -- then discarded it, deleting
the stale PID file and restarting silently, once per tool call, forever. Users
were left with an observer-start.log full of success lines and an observer
that never completed a single analysis cycle.
Record the "well-formed PID that is no longer alive" case, count consecutive
non-survivals in ${PROJECT_DIR}/.observer-nosurvive-count, and log one
explanatory warning when the streak reaches ECC_OBSERVER_NOSURVIVE_WARN_AFTER
(default 3). Warning fires on equality so a persistent failure logs once per
streak rather than once per tool call; finding the observer alive resets the
streak. The Windows-specific explanation is gated on uname so Linux/macOS
users are pointed at observer.log instead of a wrong diagnosis.
Counting happens in the caller, not inside _CHECK_OBSERVER_RUNNING, because
that function is invoked once per PID file and again under the start lock.
The PowerShell backgrounding rewrite is deliberately not included: it cannot
be exercised on a non-Windows machine, and untested process-spawning code is
a worse outcome than an accurate diagnostic.
* docs(continuous-learning-v2): state observer platform support and the new warn threshold
The observer's Windows limitation was only discoverable by hitting it. Record
it next to observer.enabled, where it is read before the flag is set, and
document ECC_OBSERVER_NOSURVIVE_WARN_AFTER so the knob added alongside the
warning does not repeat the undocumented-env-var problem tracked in #2573.
zh-TW is intentionally left alone: translation parity is not enforced here and
the repo rejects blind translation imports without translator review.
* fix(continuous-learning-v2): serialize the non-survival streak under the lazy-start lock
observe.sh runs on every tool call, so the streak read-modify-write could race
between concurrent invocations -- losing an increment or logging the warning
twice. That is the same class of bug the signal counter hit in #2296, and this
repo's rule is to never fall back to an unlocked read-modify-write.
Rather than add a second lock, move the increment into _START_OBSERVER_LOGGED.
All three of its call sites already run inside the lazy-start lock
(flock / lockfile / mkdir), so the update is serialized with no new machinery.
Counting at the restart instead of at detection also means N racing hooks
record one death rather than N.
The reset stays in the caller: it is an idempotent unlink, not a
read-modify-write, so it needs no lock.
Adds a regression case pinning the increment inside _START_OBSERVER_LOGGED and
asserting all three call sites remain locked.
* fix(continuous-learning-v2): harden the non-survival threshold and warning output
Three review findings on the #2489 diagnostic:
- An all-zero threshold silently disabled it. `00` passes a digits-only check
but compares as zero, and the streak only grows, so the warning could never
fire. Normalize with base-10 arithmetic and fall back to the default for
anything below 1. Base 10 is forced explicitly because a leading zero would
otherwise be read as octal, and `08` is an arithmetic error that would abort
the hook under `set -e`. The same normalization now guards the streak read.
- An unwritable log silently swallowed the diagnostic. Build the message once
and fall back to stderr when the append fails. This cannot spam: the block
runs once per streak, not once per tool call. The counter write keeps its
`|| true` -- observe.sh runs on every tool call and the repo rule is that
hooks exit 0 on non-critical errors, so a full disk must not break tool use.
- The live-PID test fixture used process.pid, which is 1 in a container and is
deliberately rejected by _CHECK_OBSERVER_RUNNING; the reset case would then
fail for the wrong reason. Use a spawned child and clean it up.
Adds a regression case for the all-zero threshold. Verified on bash 3.2 (the
macOS CI runner shell) as well as bash 5.
* fix(continuous-learning-v2): warn only on a persisted streak increment
If the counter write fails, the file stays below the threshold, so every later
hook invocation rereads it, re-increments in memory, hits the equality check
and warns again -- turning the once-per-streak diagnostic into once-per-tool-
call spam. That is worse in exactly the case the stderr fallback added in the
previous commit was meant to cover, since a disk that cannot take the log
usually cannot take the counter either.
Gate the warning on the write succeeding. The write stays non-fatal: it runs
as an `if` condition, so `set -e` is satisfied and an unwritable counter costs
a delayed diagnostic rather than a broken tool call.
Tests: an unwritable counter must stay silent across repeated invocations while
the hook still exits 0, and a leading-zero threshold ("08") must be read as
decimal -- "00" alone did not exercise the base-10 conversion, since it is zero
either way.
---------
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
* fix(skill-evolution): wire Skill PostToolUse tracker so skill-health shows real runs (#2463)
recordSkillExecution() had no production callers, so
~/.claude/state/skill-runs.jsonl was never written and
`scripts/skills-health.js --dashboard` always reported 0 runs.
Adds scripts/hooks/skill-run-tracker.js and registers it as an async
PostToolUse hook (matcher: Skill) in posttooluse-dispatcher.js, which is now
the single PostToolUse entrypoint on main.
Addresses the privacy and bounds review on #2555's sibling PR:
- No prompt text is persisted. task_description is synthesized as
"Skill invocation: <skill_id>"; tool_input.task_description/description/
prompt are never read.
- Every persisted string is bounded and charset-restricted. A skill id is an
identifier, so free text, newlines, or an over-long value are dropped rather
than truncated and written through.
- The JSONL sink is created 0600 and re-tightened on each append, repairing
files written before this bound existed.
- The sink is capped at MAX_RUN_RECORDS (5000), trimmed oldest-first, so the
append-only file can no longer grow without limit.
Tests cover the privacy guarantee (no prompt text reaches a record), the
identifier bounds, the file mode on POSIX, and the retention cap.
* fix(skill-evolution): re-register the tracker for PostToolUseFailure
The rebase onto current main dropped the hooks.json entry, which silently
resurrected the P1 from the earlier review round: deriveOutcome() still
branches on hook_event_name === 'PostToolUseFailure', but the PostToolUse
dispatcher does not fan that event out, so the branch was unreachable in
production. Hard Skill failures were dropped from telemetry entirely, which
inflates the dashboard success rate — the opposite of what #2463 asks for.
Restores the dedicated PostToolUseFailure entry (matcher Skill, id
post:skill:track, same run-with-flags wrapper and standard,strict gating as
the dispatcher registration). Verified end-to-end: a PostToolUseFailure
payload piped through run-with-flags now records outcome "failure".
Adds a regression test asserting the registration so a future rebase cannot
quietly drop it again.
---------
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
* fix: disable Claude co-author attribution by default
* fix: harden default co-author opt-out and correct the docs
Follow-up on the co-author default in this PR.
- Remove the existsSync/writeFileSync race in the installer settings write
(CodeQL js/file-system-race, high). A single guarded read now covers the
fresh-install case, and unreadable or non-object settings are left untouched.
- Respect `attribution` as an explicit user choice. It supersedes
`includeCoAuthoredBy` in Claude Code 2.1.x, so a user who configured it would
otherwise have had a dead key written into their settings.
- Share one opt-out rule via scripts/lib/claude-commit-attribution.js instead of
duplicating it across the installer and plugin setup.
- Update the git-workflow rule and its nine mirrors and translations, which
still told users ECC does not ship this setting.
We keep writing the deprecated `includeCoAuthoredBy` key rather than
`attribution`: unknown keys fail Claude Code settings validation, so writing
`attribution` would break users on older versions.
* feat: add council-multi-model skill (heterogeneous Codex review)
Rebased onto latest main to resolve the merge conflict (the branch had gone
DIRTY as main advanced). Trimmed to just the skill files (no top-level
README/AGENTS edits), mirroring the merged #2381. Previously reviewed
favorably by greptile/coderabbit/daltino.
* feat: add Entry B (independent parallel propose + aggregate, MoA-style) alongside Entry A (review)
Splits the skill into two entries depending on what already exists:
Entry A (unchanged) reviews an existing draft. New Entry B has every
voice (Claude x3 + Codex if available) answer the same question fully
independently and in parallel, then aggregates without collapsing
disagreement or blending incompatible approaches into one hybrid.
For the heaviest decisions the two chain: B first, then A's review
step on the aggregation -- with an explicit honesty caveat when Codex
already proposed in B and so cannot independently judge the result.
* feat: prefer Codex MCP tool over the SDK script when available
mcp__codex__codex is now the primary path for both Entry A's
heterogeneous review and Entry B's independent proposal -- zero relay,
talks directly to OpenAI's backend, no temp file or shell escaping
needed. The openai-codex SDK script becomes the fallback for sessions
without that MCP tool configured; behavior and guardrails (read-only,
verbatim quoting, explicit 'absent' labeling) are unchanged.
* fix: register council-multi-model install path
* docs: sync skill catalog count
* fix: publish council-multi-model skill
* fix: harden council multi-model fallback
* docs: sync remaining skill count
* fix: narrow multi-model council to bounded review
* fix: address council adapter review feedback
* fix(council-multi-model): enforce tool-less Codex review
* fix(council-multi-model): close Codex tool boundary
---------
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
* ci: add ruff + mypy to the Python CI job and fix pyproject tool config
The python-tests job runs pytest but not lint/type checks, and the ruff
and mypy configuration in pyproject.toml was silently broken, so neither
tool could run at all.
- add ruff and mypy steps to the existing python-tests job
- fix invalid pyproject keys: [tool.ruff] src-path -> src,
[tool.mypy] src_paths -> mypy_path
- ignore ruff UP042 (the (str, Enum) mixin is intentional)
- resolve ruff findings (unused/unsorted imports) across src and tests
- fix mypy errors in tools/executor.py and prompt/builder.py
* fix(ci): satisfy Python lint after main refresh
---------
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
* feat(skills): add dev-team skill — multi-persona collaborative session
Adds skills/dev-team/SKILL.md, a community skill inspired by the
BMAD Method's "party mode": PM, Architect, Developer, and QA respond
to the same topic in parallel, then a synthesis step names tensions
explicitly instead of averaging them.
Reads PROJECT-CONTEXT.md from the repo root when present, and offers
to generate it when missing, folding in the closed project-context
skill's (#2310) generation workflow per affaan-m's review — that
skill's premise (every agent reads the file) wasn't implemented
anywhere, so the capability now lives directly in the one skill that
actually reads it.
Rebuilt on current upstream/main as a skill-only diff: the shared
format-code.ts Windows fix and github-coordination branch-coverage
tests that were previously bundled here (and duplicated across the
story-lifecycle and project-context sibling PRs) now live in #2459.
* fix(manifests): register dev-team skill in workflow-quality install module
* fix(docs): repair README lint errors and Windows hook-install path regression
Fixes CI inherited from the README 2.1 restructure (19b05476):
- MD058: blank lines around tables (delegation map, Codex role configs)
- MD001: Option A/B headings under Ecosystem Tools h2 jump to h4
- MD024: duplicate 'What's included' headings (Codex, Copilot sections)
- restore %USERPROFILE%\\.claude escaping required by
tests/scripts/manual-hook-install-docs.test.js
* feat(skills): address review — trust boundary, harness-neutral I/O, contract test
Address maintainer review on #2309:
- untrusted-context boundary now travels with every persona prompt:
inline label on the context section, personas marked analysis-only
with no state-changing tool use
- personas receive a bounded declarative summary (≤150 words, fixed
fields, secrets and imperative content stripped) — never the raw
PROJECT-CONTEXT.md
- context loading uses harness-native file tools; POSIX-only
'test -f && cat' removed
- all references resolve on main: story-lifecycle follow-up replaced
with /plan and epic-* commands, ecc:plan-prd corrected to the
/plan-prd command; boundary vs team-builder and council made explicit
- added tests/docs/dev-team-skill.test.js contract test (roles,
parallel dispatch, synthesis guardrails, trust boundary, registration)
* docs: refresh Turkish skill count
* ci: retrigger checks (flaky stop-hooks-stdout timeout on macos node20 npm cell)
---------
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
* fix(hooks): context-monitor noise — loop-detection false positives and per-call cost-warning spam
Two independent noise sources in the PostToolUse context monitor injected
agent-facing warnings on nearly every tool call:
1. LOOP WARNING false positives. hashToolCall() hashed only the first 160
chars of a Bash command, so distinct long commands sharing a prefix
(heredocs, long one-liners) collided and consecutive DIFFERENT calls
looked like a stuck loop. Additionally LOOP_THRESHOLD=3 against a
5-entry ring buffer fired on legitimate repetition (retries, polling).
Fix: hash the full command (digest truncated, not the input — same
treatment the Edit/Write branch already got), and require all 5 of the
last 5 calls to be identical before warning.
2. COST NOTICE spam. run() deduped warnings on exact message text, but the
cost figure embedded in the text moves on nearly every call, so once a
session crossed $5 a 'new' COST NOTICE was injected per tool call for
the rest of the session. Context warnings had the same defect via the
remaining-% figure. Fix: dedupe on a stable per-tier key
(cost:notice/warning/critical, context:warning/critical, scope) so each
tier fires exactly once and re-fires only on genuine escalation. The
existing ECC_CONTEXT_MONITOR_COST_WARNINGS opt-out is unchanged.
Tests: loop threshold updated (5-of-5 fires, 4-of-5 does not), long
shared-prefix Bash hash regression, and a run()-level tier-dedupe test
(notice fires once, silent on cost tick, re-emits on escalation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: keep context warning state immutable
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
* feat(session-start): rank injected instincts by project/stack relevance
Instinct selection at SessionStart ranked purely by confidence, so a
high-confidence instinct about an unrelated stack could take an injection
slot from a lower-confidence instinct that is actually relevant to the
current project.
Rank by confidence + location/stack relevance instead: project-scoped
instincts, and instincts whose domain/trigger matches the detected stack
(languages/frameworks via detectProjectType, plus terraform/dbt markers),
get a small additive boost. The confidence>=threshold floor and the
injection cap are unchanged, and ranking degrades to confidence-only when
nothing matches or when ECC_INSTINCT_RELEVANCE_RANKING is set to off.
The ranking helpers live in scripts/lib/instinct-relevance.js with unit
coverage in tests/lib/, plus an end-to-end ordering test in tests/hooks/.
Completes part (b) of #2371; part (a) (configurable count + threshold)
shipped in #2413.
Fixes#2371
* refactor(session-start): drop redundant confidence tiebreaker in instinct sort
Greptile flagged that the secondary `right.confidence` comparison in
summarizeActiveInstincts' sort was dead code when relevance ranking is
disabled and, when enabled, was reached only on a floating-point tie of the
combined score — where it skipped the intended scope-label tiebreaker.
Remove it: the primary combined-score comparison already reduces to
confidence-only ordering when relevance is off, so behavior there is
unchanged; a genuine combined-score tie now falls through to the documented
scope-first, then id, order.
* test: isolate instinct relevance environment
---------
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
Feedback sent from the canvas only reached an agent through a live
/api/await long poll. When a turn ended with no await parked,
queueFeedback wrote the message to sessions.json and nothing ever
consumed it, so sending appeared to do nothing at all. The presence pill
made it worse: workingKeys had no expiry and the feedback handler never
broadcast presence, so it froze on "agent working" while nobody was
listening.
Delivery:
- Add the stop:plan-canvas-pending hook. It drains undelivered feedback
and blocks the Stop, handing the messages to the agent, so a canvas
message lands even when no await is running. Scoped to sessions under
cwd so parallel agents cannot swallow each other's feedback; set
ECC_PLAN_CANVAS_STOP_SCOPE=all to widen. Honors stop_hook_active and
fails open on every error path.
- run-with-flags.js did not await a hook's run(), so any async hook
silently degraded to pass-through. Fixed; plan-canvas-pending is the
only async hook today.
Presence and indicators:
- Presence is now ended/typing/thinking/listening/queued/waiting.
thinking and typing self-expire (90s/30s) and a 5s sweep pushes the
decay to an idle browser, so the pill can no longer stick.
- Broadcast presence when feedback is queued, and clear the activity
state when an agent reply lands.
- Add POST /api/session/:key/typing so agents can drive the indicator.
- Chat shows an animated dots bubble for thinking and typing, plus an
explicit note when a message is queued with nobody listening.
Respects prefers-reduced-motion.
- Send status reports what actually happened instead of always claiming
the agent will pick it up.
CLI and skill:
- Add `ecc-plan-canvas pending` and `typing <file> --state ...`.
- SKILL.md documents background await as the primary pattern and makes
replying in the canvas mandatory.
Tests: 6 new server cases covering queued presence, the typing endpoint,
state expiry and the sweep, plus a new hook suite covering delivery,
drain-once, stop_hook_active, cwd scoping and fail-open.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ecc memory writes and --body-file reads fail on Windows. sameFileIdentity()
compares the dev field of a path-based stat against a handle-based fstat, and
libuv 1.49.0 through 1.50.x resolve path-based stat() and lstat() on Windows
through GetFileInformationByName, which leaves the volume serial unset while
fstat() reports it. The comparison never matches, so the TOCTOU guard rejects
every operation.
Keep the inode strict and compare dev only when both sides report one. POSIX
always reports a non-zero dev, so the original strict behaviour is preserved
there.
Request the guard's stats as BigInt. On the affected libuv versions dev is 0,
which leaves the inode as the only identity signal, and Windows file IDs run
past Number.MAX_SAFE_INTEGER where two distinct files can collapse to the same
number-valued inode.
Fixes#2626
main has been red since the Itô skill series landed. Two independent
problems, both in test files rather than shipped behavior:
- tests/ci/ito-inference-skill.test.js asserted a stale copy of the
capability:ito-compute description. #2706 added device revocation to
the lifecycle and updated manifests/install-components.json, but this
expectation was not updated with it. The manifest is the shipped
artifact, so the test expectation is what was wrong.
- three ito test files matched YAML frontmatter indentation with two
literal spaces inside a regex literal, which trips no-regex-spaces.
Replaced with an explicit ` {2}` quantifier, which matches identically.
The basket-compare occurrence was not visible in CI: npm run lint is
`eslint . && markdownlint ...`, so ESLint reported only the first file
and stopped. Fixing only what CI printed would have left main red on the
next run. The markdownlint half of that chain had therefore never
executed; it passes.
Verified on this branch: full suite 3707/3707, repo-wide ESLint clean,
and markdownlint clean under the exact CI glob.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: ship new Ito skills through install manifests
* ci: audit shipped dependencies separately from tooling
* test(release): pass previous version to heading helper
release.test.js only greps release.sh for one of the five
update_latest_release_heading call sites, and plugin-manifest.test.js
only checks the headings committed today. Neither executes the rewrite,
so a helper that silently no-ops on a missing heading would ship green.
Extract the embedded node program from release.sh and run it against
fixtures to pin the fail-closed contract: bump stable and prerelease
headings, leave the rest of the file untouched, and exit non-zero
without writing when no heading matches. Also pin all five call sites so
the docs/zh-CN/README.md regression cannot recur.
Runs standalone via node tests/scripts/release-heading.test.js.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>