Skip to content

Commit 614892c

Browse files
author
evolver-publish
committed
Release v1.89.3
1 parent 95c26f5 commit 614892c

77 files changed

Lines changed: 1678 additions & 250 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.ja-JP.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
# 🧬 Evolver
22

3-
[![GitHub stars](https://img.shields.io/github/stars/EvoMap/evolver?style=social)](https://github.com/EvoMap/evolver/stargazers)
3+
[![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
44
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
55
[![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6-
[![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
76
[![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
8-
[![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
97
[![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
108

119
![Evolver Cover](assets/cover.png)

README.ko-KR.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
# 🧬 Evolver
22

3-
[![GitHub stars](https://img.shields.io/github/stars/EvoMap/evolver?style=social)](https://github.com/EvoMap/evolver/stargazers)
3+
[![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
44
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
55
[![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6-
[![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
76
[![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
8-
[![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
97
[![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
108

119
![Evolver Cover](assets/cover.png)

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
# 🧬 Evolver
22

3-
[![GitHub stars](https://img.shields.io/github/stars/EvoMap/evolver?style=social)](https://github.com/EvoMap/evolver/stargazers)
3+
[![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
44
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
55
[![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6-
[![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
76
[![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
8-
[![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
97
[![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
108

119
![Evolver Cover](assets/cover.png)

README.zh-CN.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
# 🧬 Evolver
22

3-
[![GitHub stars](https://img.shields.io/github/stars/EvoMap/evolver?style=social)](https://github.com/EvoMap/evolver/stargazers)
3+
[![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
44
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
55
[![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6-
[![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
76
[![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
8-
[![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
97
[![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
108

119
![Evolver Cover](assets/cover.png)

assets/gep/genes.seed.json

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,257 @@
240240
"tier": "cheap",
241241
"reasoning_level": "low"
242242
}
243+
},
244+
{
245+
"type": "Gene",
246+
"id": "gene_publish_feishu_doc",
247+
"category": "innovate",
248+
"signals_match": [
249+
"publish_markdown_to_feishu",
250+
"create_feishu_doc",
251+
"export_report_to_feishu",
252+
"把结果发到飞书文档",
253+
"发布飞书文档",
254+
"把报告导出到飞书",
255+
"publish results to a feishu doc",
256+
"export notes to lark document",
257+
"飞书文档",
258+
"发布到飞书",
259+
"导出到飞书",
260+
"发到飞书",
261+
"lark文档",
262+
"飞书文档|lark doc|feishu doc"
263+
],
264+
"strategy": [
265+
"Verify the toolchain: run `lark-cli doctor` and require ok:true with at least one ready identity",
266+
"Always use the Docs v2 API (v1 is deprecated): pass `--api-version v2`",
267+
"Write the body as Lark-flavored Markdown to a temp file and pass `--content @file.md --doc-format markdown` to avoid shell-escaping bugs",
268+
"Create with the user identity so the doc is human-owned: `lark-cli docs +create --api-version v2 --as user --doc-format markdown --content @file.md`",
269+
"To place it in a folder or wiki add `--parent-token <token>` (use `--parent-position my_library` for the personal space)",
270+
"Parse data.document.url from the JSON response and return it to the user; use `docs +update --api-version v2` with the document_id to amend instead of recreating"
271+
],
272+
"validation": [
273+
"node --version"
274+
],
275+
"constraints": {
276+
"max_files": 2,
277+
"forbidden_paths": [
278+
".git",
279+
"node_modules",
280+
"~/.lark-cli/config.json"
281+
]
282+
},
283+
"preconditions": [
284+
"lark-cli installed and on PATH (npm i -g @larksuite/cli)",
285+
"lark-cli auth status reports a ready user or bot identity"
286+
],
287+
"summary": "Publish Markdown content as a Feishu/Lark document via the official lark-cli (Docs v2). Use --as user for human-owned docs and @file content for long bodies; return the resulting document URL.",
288+
"schema_version": "1.6.0",
289+
"epigenetic_marks": [],
290+
"learning_history": [],
291+
"anti_patterns": [],
292+
"routing_hint": null,
293+
"tool_policy": null,
294+
"avoid": [
295+
"using the deprecated Docs v1 API or the v1 --markdown flag",
296+
"passing long markdown inline (shell-escaping corrupts it) instead of --content @file",
297+
"overwriting ~/.lark-cli/config.json (holds the app secret and tokens)"
298+
],
299+
"asset_id": "sha256:9ed275fd6394567d0eb6c0fda45193bbeaba7bd84941ea4e75eb7fc859fb0dcf"
300+
},
301+
{
302+
"type": "Gene",
303+
"id": "gene_conventional_git_commit",
304+
"category": "optimize",
305+
"signals_match": [
306+
"git_commit",
307+
"create_commit",
308+
"commit_changes",
309+
"conventional_commit",
310+
"提交代码",
311+
"生成提交信息",
312+
"write a commit message",
313+
"stage and commit"
314+
],
315+
"strategy": [
316+
"Inspect the change: `git diff --staged` if anything is staged, else `git diff`, plus `git status --porcelain`",
317+
"Pick a Conventional Commits type (feat/fix/docs/style/refactor/perf/test/build/ci/chore/revert) and optional scope from what actually changed",
318+
"Stage logically-grouped files explicitly (git add <paths>); NEVER stage or commit secrets (.env, credentials, private keys)",
319+
"Write a present-tense imperative description under 72 chars; add a body/footer for breaking changes (type! or BREAKING CHANGE:) and issue refs (Closes #N)",
320+
"Commit one logical change with `git commit -m` (heredoc for multi-line)",
321+
"Safety: never touch git config, never --force/hard-reset/--no-verify without explicit request, never force-push main; if a hook fails, fix and make a NEW commit (do not amend)"
322+
],
323+
"validation": [
324+
"node --version"
325+
],
326+
"constraints": {
327+
"max_files": 50,
328+
"forbidden_paths": [
329+
".git",
330+
"node_modules"
331+
]
332+
},
333+
"preconditions": [
334+
"a git repository with staged or unstaged changes"
335+
],
336+
"summary": "Create a Conventional Commits-style git commit: analyze the diff to pick type/scope, stage logical groups (never secrets), and write an imperative <72-char message.",
337+
"schema_version": "1.6.0",
338+
"epigenetic_marks": [],
339+
"learning_history": [],
340+
"anti_patterns": [],
341+
"routing_hint": null,
342+
"tool_policy": null,
343+
"avoid": [
344+
"committing secrets or unrelated changes in one commit",
345+
"amending or force-pushing to bypass a failing hook",
346+
"past-tense or vague messages like \"updated stuff\""
347+
],
348+
"asset_id": "sha256:505c207b9984c397255daed61c5f24fb3bfcadedb803d2a4eaa429457f08cd2f"
349+
},
350+
{
351+
"type": "Gene",
352+
"id": "gene_poll_bugbot_review",
353+
"category": "optimize",
354+
"signals_match": [
355+
"poll_bugbot",
356+
"bugbot_review",
357+
"wait_for_ci_review",
358+
"pr_review_gate",
359+
"等bugbot",
360+
"等待评审",
361+
"check bugbot",
362+
"review the pr",
363+
"pr opened"
364+
],
365+
"strategy": [
366+
"Poll the \"Cursor Bugbot\" check via `gh pr view --json statusCheckRollup` every ~60s until status=COMPLETED (cap ~10min); filter by name, not index",
367+
"On SUCCESS: safe to merge ONLY if no other required check is red AND zero open inline comments from the cursor[bot] login (note the [bot] suffix)",
368+
"On NEUTRAL: do NOT treat as pass — fetch inline comments `gh api repos/:o/:r/pulls/:n/comments` filtered to user.login==\"cursor[bot]\", surface path/line/severity, hand back to the human",
369+
"On FAILURE/ACTION_REQUIRED: surface findings, do not merge",
370+
"Auto-merge (squash + delete-branch) only when explicitly authorized AND conclusion is SUCCESS; merge conflicts/CI-red/required-review surface verbatim, never auto-fixed here"
371+
],
372+
"validation": [
373+
"node --version"
374+
],
375+
"constraints": {
376+
"max_files": 1,
377+
"forbidden_paths": [
378+
".git",
379+
"node_modules"
380+
]
381+
},
382+
"preconditions": [
383+
"an open GitHub PR in a repo where Cursor Bugbot runs"
384+
],
385+
"summary": "Wait for Cursor Bugbot on a GitHub PR, then gate on the conclusion: SUCCESS may merge, NEUTRAL/FAILURE always pauses to surface inline findings to the human.",
386+
"schema_version": "1.6.0",
387+
"epigenetic_marks": [],
388+
"learning_history": [],
389+
"anti_patterns": [],
390+
"routing_hint": null,
391+
"tool_policy": null,
392+
"avoid": [
393+
"treating NEUTRAL as pass (it has shipped real bugs before)",
394+
"filtering comments on \"cursor\" instead of \"cursor[bot]\" (silently returns nothing)",
395+
"auto-merging without explicit authorization or with CI red"
396+
],
397+
"asset_id": "sha256:0f50f4cfecb0e6f3a9bd3c9c0a426e56f4f1c0230b4836a9471b38e499410ea9"
398+
},
399+
{
400+
"type": "Gene",
401+
"id": "gene_gateway_timeout_recovery",
402+
"category": "repair",
403+
"signals_match": [
404+
"gateway_timeout",
405+
"upstream_timeout",
406+
"http_524",
407+
"request_timed_out",
408+
"超时了",
409+
"网关超时",
410+
"遇到超时",
411+
"retry on timeout",
412+
"operation timed out"
413+
],
414+
"strategy": [
415+
"Treat it as transient or size-driven, not a logic failure; do not report it as a hard failure before recovering",
416+
"Retry the SAME operation verbatim exactly ONCE (a large fraction clear on immediate retry); do not loop",
417+
"If it times out again, STOP retrying the monolith: split the work along a natural seam (per-file/dir/endpoint/record/section/time-window) into small independent units",
418+
"Dispatch the units as parallel subagents in a single batch so each finishes under the gateway deadline; merge their results",
419+
"If one unit itself times out, apply this same procedure recursively to that slice"
420+
],
421+
"validation": [
422+
"node --version"
423+
],
424+
"constraints": {
425+
"max_files": 1,
426+
"forbidden_paths": [
427+
".git",
428+
"node_modules"
429+
]
430+
},
431+
"preconditions": [
432+
"a tool call / fetch / subagent / long command returned a gateway-class timeout (524/522/502/504)"
433+
],
434+
"summary": "Recover from a gateway/upstream timeout: retry the same call once, and if it still times out, decompose the work into parallel subagents and merge — never loop the monolithic call.",
435+
"schema_version": "1.6.0",
436+
"epigenetic_marks": [],
437+
"learning_history": [],
438+
"anti_patterns": [],
439+
"routing_hint": null,
440+
"tool_policy": null,
441+
"avoid": [
442+
"retrying the same large call more than once",
443+
"serial retries instead of parallel decomposition",
444+
"surfacing the timeout as a hard failure before recovering"
445+
],
446+
"asset_id": "sha256:63c4251dcd8308030194f797051c08672691b52553e00b0eb33772c215712acc"
447+
},
448+
{
449+
"type": "Gene",
450+
"id": "gene_github_webhook_listener",
451+
"category": "innovate",
452+
"signals_match": [
453+
"github_webhook_listener",
454+
"bugbot_webhook",
455+
"passive_pr_notifications",
456+
"设置webhook",
457+
"部署webhook监听",
458+
"notify when bugbot finishes",
459+
"webhook tunnel"
460+
],
461+
"strategy": [
462+
"Run the idempotent deploy: a loopback Python listener (127.0.0.1:8644) validating GitHub X-Hub-Signature-256 HMAC via hmac.compare_digest, writing vetted payloads to ~/.claude/inbox/",
463+
"Expose it via a cloudflared quick tunnel (outbound-only, no inbound port); a path-watcher re-PATCHes the GitHub webhook config whenever the tunnel URL changes",
464+
"Keep listener + tunnel alive with systemd --user units hardened (ProtectSystem=strict, NoNewPrivileges, MemoryDenyWriteExecute); a SessionStart hook drains the inbox and re-validates PR state via gh api before surfacing",
465+
"Security invariants: HMAC on every request, X-GitHub-Delivery dedup against replay, write-only sink (never exec/template/deserialize payload), file modes secret 0600 / inbox 0700",
466+
"Add repos with deploy.sh --repos; rotate the secret every 90 days (rotate-secret.sh) and immediately on any leak; never trust the inbox payload without re-fetching"
467+
],
468+
"validation": [
469+
"node --version"
470+
],
471+
"constraints": {
472+
"max_files": 20,
473+
"forbidden_paths": [
474+
".git",
475+
"node_modules"
476+
]
477+
},
478+
"preconditions": [
479+
"a developer machine with systemd --user and cloudflared available"
480+
],
481+
"summary": "Deploy a per-developer GitHub webhook listener (HMAC-validated, cloudflared tunnel, systemd-kept) that drops PR/Bugbot events into ~/.claude/inbox for the next session to surface.",
482+
"schema_version": "1.6.0",
483+
"epigenetic_marks": [],
484+
"learning_history": [],
485+
"anti_patterns": [],
486+
"routing_hint": null,
487+
"tool_policy": null,
488+
"avoid": [
489+
"opening an inbound port instead of an outbound cloudflared tunnel",
490+
"trusting the webhook payload without HMAC validation and PR re-fetch",
491+
"execing or deserializing anything from the payload"
492+
],
493+
"asset_id": "sha256:ac2a2f185390aef37996651ef21355f4beb437049e52a6ca3898619a8d648084"
243494
}
244495
]
245496
}

index.js

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -268,23 +268,17 @@ function getLastSignals(statePath) {
268268

269269
// Singleton Guard - prevent multiple evolver daemon instances.
270270
//
271-
// Round-4: pidfile location previously defaulted to __dirname, which is a
272-
// DIFFERENT path per install mode -- /usr/local/lib/node_modules/... for a
273-
// global install, the dev-clone path for `node index.js`, a transient
274-
// $NPM_CACHE/_npx/<hash> for `npx evolver`. Two daemons launched under
275-
// different install modes never saw each other's lock and could run
276-
// concurrently against the same ~/.evomap/node_secret, ping-ponging on
277-
// secret rotation and silently entering reauth backoff -- the user-
278-
// reported "first launch ok, idle, then dead forever" pattern. Default
279-
// now lives under the per-user state dir so all install modes converge.
280-
// EVOLVER_LOCK_DIR still overrides for tests / sandboxed runs.
281-
function getLockFilePath() {
282-
if (process.env.EVOLVER_LOCK_DIR) {
283-
return path.join(process.env.EVOLVER_LOCK_DIR, 'evolver.pid');
284-
}
285-
// os.homedir() is cross-platform; process.env.HOME is unset on Windows.
286-
return path.join(os.homedir(), '.evomap', 'instance.lock');
287-
}
271+
// Lock location + lease tunables live in src/adapters/scripts/_lockPaths.js
272+
// (issue #176): the session-start hook's auto-restart guard needs the exact
273+
// same resolution, and inlining it in both places drifted. The Round-4
274+
// (per-install-mode pidfile convergence) and Round-9 (lease staleness)
275+
// history notes moved there with the code.
276+
const {
277+
getLockFilePath,
278+
lockIsStaleByLease: _lockIsStaleByLease,
279+
STALE_LOCK_TTL_MS,
280+
LOCK_REFRESH_MS,
281+
} = require('./src/adapters/scripts/_lockPaths');
288282

289283
function _writeLockAtomic(lockFile, payload) {
290284
// Round-6 (§19.8): the previous implementation used tmp + rename, which
@@ -372,38 +366,11 @@ function _lockPayload() {
372366
});
373367
}
374368

375-
// Round-9: lease tunables for the daemon lock. A live daemon refreshes the
376-
// lock mtime every LOCK_REFRESH_MS; a lock whose mtime is older than
377-
// STALE_LOCK_TTL_MS (and that was written by a lease-aware daemon) is
378-
// treated as stale even if its PID happens to be alive -- closing the
379-
// "crash + PID reuse -> new daemon silently refuses to start" hole and the
380-
// "SIGKILL leaves a stale lock nobody reclaims" hole. The TTL is well above
381-
// the heartbeat interval (default 6min) so a healthy daemon never trips it.
382-
// On Windows, SIGTERM is implemented as TerminateProcess() (not a catchable
383-
// signal), so the shutdown() handler that calls releaseLock() never runs.
384-
// The lock file stays on disk with the dead PID. Reduce the TTL on Windows
385-
// so a subsequent start doesn't wait 15 minutes to reclaim the stale lock.
386-
// Unix dropped from 15 min -> 5 min so a wedged daemon does not block takeover
387-
// for a quarter hour. 5 min is still 2.5x the 2-min Unix refresh cadence.
388-
// Windows 3 min TTL gets a 1-min refresh (3x margin) since 2-min refresh left
389-
// only 1.5x margin against transient FS hiccups.
390-
const STALE_LOCK_TTL_MS = process.platform === 'win32' ? 3 * 60_000 : 5 * 60_000;
391-
const LOCK_REFRESH_MS = process.platform === 'win32' ? 1 * 60_000 : 2 * 60_000;
369+
// STALE_LOCK_TTL_MS / LOCK_REFRESH_MS / _lockIsStaleByLease come from
370+
// src/adapters/scripts/_lockPaths.js (required next to getLockFilePath
371+
// above) — see issue #176 and the Round-9 history note in that module.
392372
let _lockRefreshTimer = null;
393373

394-
// Returns true if the lock was written by a lease-aware daemon AND its
395-
// mtime is older than the stale TTL -- i.e. no live owner is refreshing it,
396-
// so it is safe to reclaim regardless of whether the recorded PID resolves.
397-
function _lockIsStaleByLease(lockFile, payload) {
398-
if (!payload || payload.lease !== true) return false;
399-
try {
400-
const ageMs = Date.now() - fs.statSync(lockFile).mtimeMs;
401-
return ageMs > STALE_LOCK_TTL_MS;
402-
} catch (_) {
403-
return false;
404-
}
405-
}
406-
407374
// Start refreshing the lock file's mtime so other processes can tell this
408375
// daemon is alive without trusting a (recyclable) PID. unref'd: it never
409376
// keeps the event loop open on its own, but fires for as long as the daemon

0 commit comments

Comments
 (0)