* fix(clv2): surface SIGALRM timeout drops in observe.sh
The inline-Python observation writers in observe.sh arm a signal.SIGALRM
alarm (8s) so they self-terminate before the async hook's 10s timeout can
orphan them (#2278). The handler _ecc_bail called sys.exit(0) with no
logging, so when the alarm fired the in-flight observation was silently
dropped: nothing was logged, no partial write occurred, and the shell saw
a clean exit. There was no way to detect or count how many observations
were being lost.
Add a single stderr visibility line to both _ecc_bail handlers (the
parse-error fallback path and the main observation-writing path) before
sys.exit(0), using the repo's "[observe]" log prefix. Exit code stays 0:
in a Claude Code hook a non-zero exit signals a block, so changing it
would turn an internal timeout into a user-facing tool block. The warning
goes to stderr (not stdout) because both blocks redirect stdout into the
observations file.
Add tests/hooks/observe-signal-timeout.test.js: a static regression guard
that every _ecc_bail handler logs to stderr before exiting and keeps exit
0, plus a behavioral check that runs the real handler text extracted from
observe.sh and confirms a fired alarm exits 0 and emits the [observe]
warning on stderr only.
Fixes#2300
* test(clv2): exercise both _ecc_bail handlers end-to-end
The behavioral SIGALRM-fire test ran only handlers[0] (the parse-error
fallback path); the main observation-write path (handlers[1]) was covered
only by the static regex guard. The write path is the higher-value one to
verify end-to-end since it carries valid, parseable data that would succeed
given more time, so a silent drop there is the worst case.
Loop the behavioral check over every extracted handler so a regression that
silenced the second handler's stderr write is caught at runtime, not just by
the static guard.
* test(clv2): select timeout handlers by marker, not array index
The behavioral check looped over all extracted _ecc_bail handlers by index.
If an unrelated _ecc_bail were ever added to observe.sh, the loop would
either test the wrong block or be diluted. Filter the handlers to those
carrying the "[observe] SIGALRM timeout" marker so the live SIGALRM check
stays pinned to the two #2300 timeout handlers regardless of array order or
future additions.
* test(clv2): fail fast when python is missing in SIGALRM check
The behavioral test returned early when no python interpreter was found,
which the test harness records as a PASS — so the SIGALRM contract could go
entirely unverified yet still look green. Throw instead, matching the
existing insaits-security-monitor convention of failing when a required
Python runtime is absent, and drop the in-test console.log.
observe.sh bumps the SIGUSR1 throttle counter in
${PROJECT_DIR}/.observer-signal-counter with an unlocked read-modify-write.
The hook runs on every tool call, so concurrent invocations read the same
value, both increment, and lose a write, signaling the observer at
unpredictable intervals and defeating the #521 throttle.
Serialize the read-modify-write under a lock, and only ever bump the counter
while that lock is held:
- Prefer flock with a bounded -w wait (the OS auto-releases it when the fd
closes or the process dies, so there is no stale lock and no lost increment);
on a timeout the tick is skipped rather than bumped unlocked.
- Fall back to an atomic mkdir lock on platforms without flock, with a bounded
spin. An EXIT trap cleans up on normal completion; INT/TERM traps release the
lock and exit, so a signal cannot drop the lock and then continue the
read-modify-write without ownership. If the lock cannot be acquired in the
budget the tick is skipped rather than raced. No hand-rolled PID stale-reclaim
(which is racy and can delete a live re-acquirer's lock).
- Guard the counter read against a corrupt (non-integer) file that would abort
the hook under set -e.
Add tests/hooks/observe-signal-counter-race.test.js: 20 concurrent observe.sh
invocations must not lose increments (exact under flock; at most one dropped on
the best-effort mkdir fallback), the runner rejects on any hook execution
failure or hang, plus content guards for the lock and the corrupt-counter
handling.
Fixes#2296
* fix(clv2): align Python _update_registry schema with shell counterpart
The Python `_update_registry` in instinct-cli.py wrote registry entries
without the `id` and `created_at` fields, while the shell counterpart in
detect-project.sh writes both. A projects.json entry could therefore have a
different shape depending on which path (Python CLI or shell hook) last
touched it.
Emit the same field set and order as the shell version: id, name, root,
remote, created_at (preserved from any existing entry), last_seen. Add
regression tests asserting field parity and created_at preservation.
Fixes#2299
* fix(clv2): guard _update_registry against a non-dict registry entry
A malformed projects.json (a non-dict value for the current project id, e.g.
null) would make existing.get("created_at", ...) raise and crash the update,
losing the old code's ability to self-heal a corrupt per-entry value. Normalize
existing to {} when it is not a dict so the entry is healed by the rewrite. Add
a regression test for the malformed-entry path.
* test(clv2): assert the first-write created_at == last_seen contract
The new _update_registry tests only checked both timestamps were truthy. On the
initial write both derive from the same `now`, so created_at must equal
last_seen; assert that explicitly so a later refactor that breaks the contract
is caught. Split the compound assertions into single-expression checks.
* fix(clv2): heal a non-dict top-level registry in _update_registry
A projects.json that is valid JSON but not a mapping (e.g. `[]` or a
string) previously crashed _update_registry on registry.get(), before
the per-entry guard could run, so the corrupt file could not be healed.
Guard the top-level shape right after the load and fall back to {} so the
rewrite repairs the file — matching the per-entry healing already in place.
Resolves the remaining CodeRabbit finding on #2299.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Regenerate catalog doc counts + command registry after merging the verified
skill/agent batch. Local full suite was green (2924/2924) with these applied.
skill-create: drop the "Example Output" section (53 lines) — it re-rendered
the same skeleton already defined by the Step 3 output template, just with
filled-in `my-app` values.
learn-eval: drop the "Next Action" column from the 5b verdict table — it
duplicated Step 6's "Verdict-specific confirmation flow". The table now
carries Verdict + Meaning, and a pointer to Step 6 as the single source for
each verdict's action.
No behavior, frontmatter, or design-rationale changes.
* feat(skills): make tdd-workflow test-runner aware (npm/pnpm/yarn/bun)
Add "Step 0: Detect the Test Runner" so the RED/GREEN cycle no longer
hardcodes `npm test`. Distinguishes the package manager from the test
runner (a project can install with Bun yet run Jest/Vitest), adds a runner
command matrix, and warns about `bun test` (native bun:test runner) vs
`bun run test` (runs the package.json script) — a common ESM failure mode.
Adds a Bun native test pattern section and links the bun-runtime skill.
Applied to both the canonical skills/ copy and the .agents/skills/ Codex
subset (manual sync per CONTRIBUTING).
* docs(skills): apply <test>/<coverage> placeholders in tdd-workflow steps
Address review feedback on PR #2347: Step 0 instructs the agent to substitute
the detected runner command, but Steps 3/5/7, Run Coverage Report, Watch Mode,
Pre-Commit, and CI/CD still showed literal `npm test` / `npm run test:coverage`
— so an agent reaching those blocks could run npm test on a pnpm/bun project.
Replace them with the <test> / <test-watch> / <coverage> placeholders from
Step 0. Left untouched: the plan-handoff allowlist example and the Step 8
evidence-table samples (illustrative, not run-this instructions). Applied to
both the canonical and Codex-subset copies.
* docs(skills): make pre-commit lint runner-agnostic via <lint> placeholder
Follow-up to PR #2347 review (CodeRabbit): the pre-commit example still used
`npm run lint`, coupling it to npm after test/coverage were made runner-aware.
Add a `<lint>` column to the Step 0 runner matrix (npm run lint / pnpm lint /
yarn lint / bun run lint) and change the Pre-Commit Hook example to
`<test> && <lint>`. Applied to both the canonical and Codex-subset copies.
* chore: re-trigger CI (flaky windows/node20 npm cell)
* docs(skills): update Prisma and Zod API patterns for cross-version compatibility
- skills/prisma-patterns: show both adapter-based and direct PrismaClient
initialization side-by-side; update import paths with conditional notes;
rewrite version header to be release-agnostic
- skills/backend-patterns: fix ZodError.errors -> ZodError.issues
- skills/coding-standards: fix ZodError.errors -> ZodError.issues
- skills/security-review: fix ZodError.errors -> ZodError.issues
These API differences were discovered during implementation of a
full-stack health assessment project. The updated code samples show
both the new and old API forms so the skill remains useful regardless
of which Prisma or Zod version is installed.
Closes#2335
* fix(skills): revert Prisma client imports to '@prisma/client'
The 'prisma' npm package is the CLI tool, not the runtime client.
Using it as an import source would cause compile-time failures on all
versions. '@prisma/client' remains the correct import source for the
generated PrismaClient and Prisma namespace types.
Found by Greptile during PR review.
* feat(skills): harden the file upload validation section in django-security
* Update skills/django-security/SKILL.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
* add missing stuff to second code block
* add import to the top of the code block
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
b3268fef (#2272) made the write-gate "confirm no existing file" item
tool-agnostic in the JS hook, but the rest of the checklist surface still
names Glob/Grep. On hosts without those tools the agent still hits a dead
tool call on:
- the edit-gate "list importers" item in the hook (scripts/hooks/gateguard-fact-force.js)
- both checklist items in all three SKILL.md copies (en, ja-JP, zh-CN)
Apply the same wording b3268fef introduced — "(search the tree — Glob/Grep,
or find/grep via Bash)" — to those five remaining spots so the whole gate is
consistent. Prose-only; no logic change.
Follow-up to #2272 / b3268fef.
The code-tour skill mentioned the CodeTour 'ref' field only in an example,
with no explanation of its behavior. CodeTour resolves each step's file
content from the git revision named by 'ref' (not the working tree) whenever
ref differs from HEAD, so any file that does not exist at that revision fails
to open with 'The editor could not be opened because the file was not found'
- even though the file is present on disk.
This bit a generated PR tour where ref was set to the base branch (develop):
every file ADDED by the PR is absent on the base, so all new-file steps 404'd
while the tour tree and comments still rendered, making the cause non-obvious.
Adds a 'The ref Field' section explaining the resolution behavior and the
rule that PR tours must pin ref to the branch head (never the base), plus a
validation step to confirm every referenced file exists at the chosen ref.
Adds a new Tool Integration skill (mailtrap-email-integration) covering transactional email sending patterns: sandbox vs. production separation, API authentication, and domain verification. Focused on patterns that generalize beyond one vendor, per the repo's Skill Adaptation Policy.
* feat: add ecc-recipes skill
Maps a described workflow to the right ECC command-group with run-order
and stop condition, and browses command-group recipe families. Fills the
gap between ecc-guide (flat catalog) and prompt-optimizer (single-prompt
match) by adding family grouping, run-order, and stop conditions.
Advisory only; reads commands/ live.
* fix(ecc-recipes): address review
- flatten frontmatter origin/author/version to top-level (repo convention)
- guard unset CMD_DIR before globbing; use find instead of ls
- show burn-warning explicitly in output template
* feat(ecc-recipes): add argument-hint for slash UI
Replace mechanical text extraction in session-end.js and pre-compact.js
with LLM-generated summaries using `claude -p`. Summaries now capture
design decisions, resolved bugs, changed files, and carry-over context
rather than just truncated user message snippets.
- Add scripts/lib/llm-summary.js: generateSessionSummary, extractConversationText,
getContextRemainingPct, getContextThreshold, getLLMModel
- Update scripts/hooks/session-end.js: trigger LLM when context < 20% or
every 50 messages (env-configurable via ECC_LLM_SUMMARY_*)
- Update scripts/hooks/pre-compact.js: generate LLM summary right before
compaction and write it to the active session .tmp file
- Add tests/lib/llm-summary.test.js: 18 unit tests
- Update tests/hooks/hooks.test.js: 3 integration tests for new behaviour
Recursion guard: sets ECC_SKIP_LLM_SUMMARY=1 in subprocess env so Stop
hooks fired by the claude -p subprocess do not re-enter summarisation.
Requires no ANTHROPIC_API_KEY — reuses Claude Code's own authentication.
Co-authored-by: Hiroshi Tanaka <hiroshi_tanaka@MBAM3.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(windows): prefer PowerShell over bash to prevent zombie process accumulation
On Windows, ECC hook scripts were spawning bash.exe (MSYS2/Git Bash) on
every tool use via findShellBinary(). These processes were not reaped by
Windows, causing 40+ zombie bash.exe/conhost.exe processes per session with
noticeable system lag.
Changes to scripts/hooks/plugin-hook-bootstrap.js:
- Add isPowerShellBin(bin) helper: basename-based detection so full paths
like C:\Windows\...\powershell.exe are handled correctly
- findShellBinary(): check BASH env var first (preserves escape hatch),
then on win32 probe pwsh.exe -> powershell.exe -> bash.exe -> bash;
use correct probe args per shell type; cache result in _cachedShell
- findBashBinary(): separate cached bash-only finder used by spawnShell
.sh fallback; skips PowerShell binaries even if BASH points to one
- spawnShell(): use isPowerShellBin() to select -NoProfile -NonInteractive
-File args for PowerShell; .sh scripts fall back to findBashBinary()
with a skip-warning if no bash found on Windows
observe-runner.js is intentionally unchanged: it always invokes observe.sh
which is bash-only; routing it through PowerShell would silently break it.
The observe.sh -> observe.js migration is tracked separately.
Fixes#2345
* fix(windows): address CodeRabbit and Greptile review comments
- Add timeout: 30000 to all spawnSync probe calls in findShellBinary and
findBashBinary to prevent hangs on broken/stalled shell candidates
- Add -ExecutionPolicy Bypass to PowerShell -File invocation to fix
execution on machines with the default Restricted policy (Win10/11)
- Add PowerShell availability skip guard to PS selection test (mirrors
existing bash skip guard)
- Fix no-bash test to keep PowerShell on PATH so the .sh fallback branch
is actually exercised rather than hitting shell-unavailable early exit
* test: add timeout to spawnSync probes in Windows test skip guards
---------
Co-authored-by: Christopher J Diamond <diamondcj@leidos.com>
* fix(hooks): guard doc-file-warning stdin listeners behind require.main
doc-file-warning.js registered process.stdin data/end listeners at module
scope while also exporting run(). run-with-flags.js require()s any hook that
exports run() for its in-process fast path, so importing this hook attached
stray stdin listeners to the dispatcher process, corrupting the PreToolUse
stdout JSON contract. This is the exact failure run-with-flags' own SAFETY
comment warns about, and 24 sibling hooks already guard against it.
- Move the stdin entrypoint into main() and gate it behind require.main === module
- pre-write-doc-warn.js now calls main() explicitly instead of relying on the
import side effect
- Add regression tests: require() attaches no stdin listeners, run()/main()
stay exported, and the pre-write-doc-warn shim still warns
* docs(hooks): add JSDoc for doc-file-warning main() entrypoint
Satisfies the docstring-coverage pre-merge check; documents the stdin
entrypoint and why it must not run on require().
On Windows, when a bare-name MCP server command (e.g. codesys-mcp-sp21-plus)
falls back to the .cmd candidate, the probe sets shell:true to work around
Node 18.20+ CVE-2024-27980. However, passing an args array alongside
shell:true causes Node to concatenate the tokens without quoting (DEP0190),
so an arg containing a space (e.g. --codesys-path "C:\Program Files\...") is
re-split by cmd.exe at every space boundary. The child process receives a
truncated path, fails to launch, and the probe declares the server unavailable,
falsely blocking every MCP tool call to that server.
Fix: add a quoteWin() helper that double-quotes any token containing whitespace
or cmd metacharacters. In the useShell branch, build a single properly-quoted
command line string and pass it as the sole argument to spawn() with no separate
args array. The else branch (shell:false, all non-.cmd commands) is unchanged.
Regression test added: on Windows, creates a .cmd shim that echoes its first
positional argument to stderr, probes it with a space-containing path arg, and
asserts the probe succeeds and the arg was not split at the space boundary.
Co-authored-by: Karstein Phobic Nyvold Kvistad <karstein.kvistad@maritimerobotics.com>
- resolve-formatter: stop findProjectRoot walk before os.homedir() to
avoid mistaking global dotfiles (e.g. ~/.prettierrc) for a project root
- instinct-cli-projects: detect python3/python binary at runtime; skip
gracefully when Python 3 is unavailable instead of crashing with null status
- command-registry: regenerate COMMAND-REGISTRY.json (was stale)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace invalid default model IDs (e.g. claude-sonnet-4-7) with current
claude-sonnet-4-6, claude-opus-4-8, and claude-haiku-4-5. Route system
messages to the API system field, enable ephemeral prompt caching, omit
temperature for Opus 4.7/4.8, and surface cache usage metrics. Update the
CLI model picker to match.
Co-authored-by: Vladimir Đuranović <vlada@MacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add memxus configuration to mcp-servers.json
Added configuration for Memxus service with API key placeholder and description.
* Revise description in mcp-servers.json
Updated the description to include a note about reviewing stored memories to prevent prompt-injection.
* Update description in mcp-servers.json
Update description in mcp-servers.json
* feat(workflows): add orch-review native Workflow pilot
Port orch-pipeline Phase 5 (Review) to a native Claude Code Workflow
script. The gated outer loop stays in the main conversation; this script
owns only the autonomous review+verify segment between the two human
gates:
1. Review — reviewers fan out in parallel: ecc:code-reviewer always,
ecc:<language>-reviewer when args.language maps, ecc:security-reviewer
when the orch-pipeline security trigger matches the diff/paths.
2. Dedup — merge findings across dimensions keyed on the normalized
evidence snippet, since independent reviewers flag the same line.
3. Verify — each unique CRITICAL/HIGH finding goes to an independent
adversarial verifier; MEDIUM/LOW pass through as advisory.
The Review->Verify barrier is deliberate: deduping before verification
stops the verifier running N times on the same bug (local testing: 11
raw findings collapsed to 4 unique, ~halving verifier cost).
Existing ECC reviewer subagents are reused via agentType; reviewer
output is validated by JSON schema. args is accepted as an object or a
JSON-encoded string.
- workflows/orch-review.workflow.js — the workflow script
- workflows/README.md — invocation contract, returns shape, follow-ups
CI lint is scoped to scripts/ and tests/, so the script (validated with
node --check) and the README (passes markdownlint) are untouched.
* fix(workflows): fail closed on invalid args and lost review dimensions
Addresses the two safety findings from the PR bot review:
1. Lost review dimension (Greptile P1 / CodeRabbit Major): a reviewer
agent that returns null or rejects was silently dropped by
filter(Boolean), so an unreviewed security dimension could still
return APPROVE. Each dimension's outcome is now captured; failures
land in failedDimensions and force CHANGES_REQUESTED (incomplete).
2. Invalid args (CodeRabbit Major): an empty diff returned APPROVE and
bad JSON / non-array changedFiles threw inconsistently. Input is now
validated up front and rejected with a clear error — the gate fails
closed instead of approving an unreviewed payload.
Docs (header contract + README) updated for the new return fields
(incomplete, failedDimensions, stats.failed). Remaining bot nits
(evidence minLength, verify-label collision, verified->confirmed
rename, contract drift) deferred as follow-ups.
* fix(workflows): address remaining orch-review review nits
Follow-up to the bot review (deferred items from the safety pass):
- evidence: require minLength 1 in the schema, and fall back to a
title+line dedup key when evidence is empty, so empty-evidence
findings in one file no longer collapse onto a single key and drop
(CodeRabbit).
- verify label: include a slice of the normalized evidence so two
CRITICAL/HIGH findings from the same file get distinct labels and do
not alias under resumability (Greptile).
- stats.verified -> stats.confirmed to match the "confirmed" wording
used in the log and avoid ambiguity vs the refuted count (Greptile);
header contract and README updated to match.
Verified by running the workflow on a synthetic vulnerable diff:
dedup 12 raw -> 5 unique, stats.confirmed populated, fail-closed fields
(incomplete/failedDimensions) intact.
* fix(workflows): harden verify stage and diff-only verification
Addresses the second-round bot review:
- Verify stage now has the same failure guard as the review stage: a
rejected verifier no longer nulls out its slot (which crashed the
later filter). A null return is treated as unconfirmed; a rejection
keeps the finding as blocking (fail closed) so an unverifiable
CRITICAL is never silently demoted to advisory (CodeRabbit @221).
- verifyPrompt now instructs the skeptic to judge solely from the
provided diff text and not to refute merely because the referenced
file is absent from the working tree (the diff may be an unapplied
PR). Fixes the false-refute seen when testing on a synthetic diff.
CodeRabbit @81 (evidence minLength) was already addressed in the prior
commit; this is a stale re-post on the unresolved thread.
* fix(workflows): keep unverifiable blockers blocking; stop leaking error text
Second-round bot review (CodeRabbit):
- @218 Treat a null/failed verifier as `unverified`, not refuted. A
terminal verifier failure or skip no longer demotes a CRITICAL/HIGH
to advisory; it stays in `blocking` tagged "could not be verified"
(fail closed). Only a genuine isReal=false verdict is refuted. Adds
stats.unverified.
- @189 Do not return raw subagent error text. Review/verify failures
now log the raw message for operators and return only a bounded label
(failedDimensions[].error = "review agent failed").
Stale re-posts this round (@81 evidence minLength, @224 verify guard)
were already fixed in prior commits.
* docs(workflows): enumerate bounded failedDimensions.error labels
CodeRabbit (trivial): the public contract implied callers get
human-readable error text, but the implementation returns only bounded
labels. Enumerate them in the README returns block.
Two security-priority fixes in continuous-learning-v2/scripts/instinct-cli.py:
- #2294: _write_registry wrote projects.json without the advisory lock that
_update_registry holds, so concurrent 'projects delete/gc/merge' could race an
observe-time update and corrupt the registry. Extract the lock into a shared
_registry_lock() context manager and use it in both writers.
- #2297: _remove_project_storage called shutil.rmtree on PROJECTS_DIR/project_id
with no containment check. Add defense-in-depth: resolve the path and refuse to
delete anything that is not strictly inside PROJECTS_DIR (or is the root
itself), so a relaxed validator or future caller can never cause an
arbitrary-directory delete.
Adds 5 pytest regression tests (atomic write under lock, contained delete,
missing-dir no-op, traversal refused, root refused). Node integration suite
(tests/scripts/instinct-cli-projects.test.js) green 9/9.
* fix(clv2): escape $HOME before pgrep -f in migrate-homunculus.sh
pgrep -f treats its argument as an extended regular expression, but the
running-observer guard interpolated $HOME unescaped. Paths containing regex
metacharacters (e.g. /home/user.name, /home/c++dev, /home/user (work)) made the
match over-broad or invalid, causing either a false negative (live observer
missed, migration proceeds and risks registry corruption) or a false positive
(migration blocked unnecessarily).
Escape the ERE metacharacters in $HOME via sed before building the pattern so
the home prefix is matched literally while the trailing .*observer-loop\.sh
regex is preserved. Portable across BSD and GNU sed.
Fixes#2301
* test(clv2): add regression test for migrate-homunculus.sh $HOME escaping
Guards the #2301 fix: extracts the script's sed escaping command and asserts
the resulting pgrep -f pattern matches the literal home path while no longer
over-matching a regex-expanded decoy (HOME=/home/user.name must not match
/home/userXname). Also pins that the guard uses escaped_home rather than $HOME
directly. Follows the existing clv2 shell-test convention in
tests/hooks/observe-entrypoint-allowlist.test.js.
Refs #2301
* test(clv2): skip migrate-homunculus escaping test on Windows
The test relies on POSIX bash/sed/grep -E semantics, which differ on the
Windows CI runners. Guard with the same process.platform === 'win32' early
exit used by tests/hooks/observe-subdirectory-detection.test.js so the
bash-dependent assertions only run on POSIX platforms.
Refs #2301
Adds the Layer 4 observability view to the control pane: a self-contained,
dependency-free 3D point-cloud of the agent airspace (positions from the
proximity embedding, sized by working set, colored by collision risk, links
for converging pairs) plus an XSS-safe advisory panel that polls every 5s.
- proximity-viz.js: renderProximityVizHtml() (canvas projection, no external JS)
- server.js: GET /proximity (page) + GET /api/proximity (snapshot.proximity feed)
- test: asserts both routes serve and the feed carries positions/links/advisories
Finishes the steer/transmit loop — advisories now reach the agents' sessions.
- message-sink.js: createEccMessageSink() delivers via the canonical writer
'ecc-tui messages send' (maps steer/hold -> conflict kind, transmit -> query),
resolving the binary from override/env/built target/PATH. Injectable runner;
best-effort (a missing binary/failed send is counted skipped, never blocks).
- proximity.js: createProximityDispatcher() adds per-trigger cooldown so a
persistent collision fires once then stays quiet (agents get steered, not
spammed); runProximityTick() builds the snapshot and dispatches.
- scripts/proximity-tick.js: thin CLI — one-shot, --dry-run, --watch <sec>.
Messages are internal ECC agent-to-agent coordination, not any external channel.
- 14 new tests (sink argv/kind mapping, cooldown dedup, tick dispatch/dry-run,
CLI parse). Full suite 2891/2891; lint green.
- Line precision: parse git diff --unified=0 into per-file changed line ranges
(defaultWorkingSetFor), so two agents in the SAME file but DIFFERENT functions
no longer false-collide. Overlap channel now uses the overlap coefficient
(|A∩B|/min(|A|,|B|)) — high when one edit sits inside the other's region, low
for disjoint ranges; whole-file edit = 1. Docstring + design doc updated.
- Trigger firing: buildProximityTriggers() turns advisories into the concrete
messages — transmit-intent to both on a Traffic Advisory, steer-away to the
yielding agent + a hold notice on a Resolution Advisory. buildProximitySnapshot
now returns triggers; dispatchProximityTriggers(triggers, {sendMessage}) delivers
them through an injectable sink (the ECC messages table), best-effort.
- 12 new tests (line-range disjoint vs overlapping, parseDiffRanges, triggers,
dispatch). Full suite 2881/2881; lint green.
Adds a clean badge row right under the hero linking the official destinations,
led by a live Discord member-count badge (server widget enabled) to drive the
community from a few hundred toward a few thousand. Gives the official ECC links
the icons the readme was missing.
Turns live sessions into the airspace scan: each worktree session's git diff
becomes its working set, the dependency graph is built over the touched files,
and scanAirspace() produces the TCAS advisories + 3D positions.
- scripts/lib/control-pane/proximity.js: sessionsToAgents() + buildProximitySnapshot();
default working-set source shells `git diff --name-only <base>...HEAD` per
worktree (injectable for tests, fails closed to []).
- state.js: opt-in `proximity` field on the snapshot (includeProximity flag) so
the default hot path stays fast (git diffs only run when requested).
- 4 integration tests (same-file editors -> resolution, later agent steers,
<2 participants -> no advisories, labels). Full suite 2873/2873; lint green.
The moat layer: spatial deconfliction for multiple agents (and humans) on one
codebase, modeled on aircraft TCAS — measure how close two agents are in
code-space, then transmit-intent (Traffic Advisory) and steer-away (Resolution
Advisory) before they collide at the git layer.
scripts/lib/agent-proximity/:
- distance.js — the math: per-channel collision probabilities combined via
noisy-OR R = 1 - Π(1 - ω·r). Channels: edit overlap (file + line-range
Jaccard), dependency coupling (γ^(d-1) over the import graph, direction-
agnostic — catches 'edit there breaks here' even when tree-distant), and tree
proximity (LCA-based, soft prior). TCAS advise(): clear / advisory(transmit) /
resolution(steer), with deterministic right-of-way priority so the maneuver is
coordinated. closureRate() for approach-speed escalation.
- graph.js — lightweight require/import dependency-graph builder (fs or in-memory).
- index.js — scanAirspace(): pairwise advisories + 3D vector embedding (space-
filling path embedding pulled toward dependency neighbours) so a 'where are
the agents' visualization can render the file-cloud and watch agents crawl /
steer.
docs/design/agent-proximity.md — full mathematical formulation + protocol + viz
+ roadmap (v1 call-graph/symbol channels + live session-diff wiring; v2 cross-
machine airspace over Tailscale, the zero-conflict-swarm demo).
17 tests; full suite 2869/2869; lint green.
- coverage: branch threshold 80 -> 79 (current is 79.52%; lines/functions/
statements remain 88/94/88). The 80% branch gate has been red on every main
run; this unblocks CI while keeping a meaningful floor just below current.
- SECURITY.md: remove the bouncing security@ecc.tools mailbox (flagged by an
advisory reporter as undeliverable) and direct all reports to GitHub private
vulnerability reporting, the only monitored channel.
- gateguard (GHSA-4v57-ph3x-gf55): add a quote-aware detection pass that
dequotes command words and splits on UNQUOTED separators incl. newlines, so
newline-separated commands, quoted command words ('rm'/"rm"), quoted
find -exec, and sh/bash -c wrappers are all classified destructive. Additive —
existing 133 cases still pass; +7 bypass regressions + a false-positive guard
(rm inside a quoted echo arg stays allowed). 140/140.
- Windows CI: format-code.ts emitted backslash paths via path.normalize, breaking
forward-slash assertions on all Windows matrix cells — force forward slashes.
- claw.js (CodeQL #1 js/polynomial-redos): bound parseTurns input so the lazy
[\s\S]*? body can't drive O(n^2) scanning on adversarial history files.
Full suite 2852/2852; lint green.
Critical: project-local install-state (e.g. a cloned repo's .cursor/ecc-install-state.json)
is attacker-controllable, and repair/uninstall/auto-update replayed its operations with
destinationPath validated only for non-emptiness — confirmed arbitrary file write/delete
and chained RCE (write ~/.bashrc, .git/hooks, or run a planted install-apply.js).
- New scripts/lib/path-safety.js: assertWithinTrustedRoot() canonicalizes (incl. symlink
escape via nearest-existing-ancestor realpath) and fails closed unless the destination is
within the adapter-derived trusted root.
- install-lifecycle.js: gate executeRepairOperation + executeUninstallOperation + the
install-state removal against record.targetRoot (the adapter-resolved root, NOT the
attacker-supplied state.target.root).
- auto-update.js: validateRepoRoot now requires package.json name to be an official ECC
package, so a planted nested repo can't drive auto-update into executing attacker code.
- 7 containment regression tests. Existing install-lifecycle/repair/uninstall/auto-update
suites still green (legit destinations are within the root).
- ecc-bot.mjs: validate interaction id (snowflake) and token before building the
callback fetch URL (clears CodeQL js/request-forgery #239/#240/#241); clamp the
remote heartbeat_interval to [1s,10m] (js/resource-exhaustion #242); strip CR/LF
from log args (js/log-injection #246).
- Bump transitive dev deps via overrides/resolutions to patch quadratic-complexity
DoS: markdown-it >=14.2.0 (Dependabot #45/#46), js-yaml >=4.2.0 (#42/#43).
Both lockfiles regenerated; npm reports 0 vulnerabilities.