@evomap/evolver 1.89.2 → 1.89.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja-JP.md +1 -3
- package/README.ko-KR.md +1 -3
- package/README.md +1 -3
- package/README.zh-CN.md +1 -3
- package/assets/gep/genes.seed.json +251 -0
- package/index.js +14 -47
- package/package.json +1 -1
- package/scripts/refresh_stars_badge.js +168 -0
- package/src/adapters/hookAdapter.js +2 -0
- package/src/adapters/scripts/_lockPaths.js +74 -0
- package/src/adapters/scripts/evolver-session-start.js +19 -27
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/forceUpdate.js +200 -7
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/autoDistillConv.js +1 -1
- package/src/gep/autoDistillLlm.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/contentHash.js +1 -1
- package/src/gep/conversationSniffer.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/execBridge.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hubFetch.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/openPRRegistry.js +1 -1
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallInject.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/tokenSavings.js +1 -1
- package/src/gep/workspaceKeychain.js +1 -1
- package/src/proxy/extensions/traceControl.js +1 -1
- package/src/proxy/index.js +4 -4
- package/src/proxy/inject.js +1 -1
- package/src/proxy/lifecycle/manager.js +11 -0
- package/src/proxy/router/messages_route.js +8 -0
- package/src/proxy/trace/extractor.js +1 -1
- package/src/proxy/trace/usage.js +1 -1
package/README.ja-JP.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# 🧬 Evolver
|
|
2
2
|
|
|
3
|
-
[](https://github.com/EvoMap/evolver/stargazers)
|
|
4
4
|
[](https://opensource.org/licenses/GPL-3.0)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://github.com/EvoMap/evolver/commits/main)
|
|
7
6
|
[](https://www.npmjs.com/package/@evomap/evolver)
|
|
8
|
-
[](https://github.com/EvoMap/evolver/issues)
|
|
9
7
|
[](https://arxiv.org/abs/2604.15097)
|
|
10
8
|
|
|
11
9
|

|
package/README.ko-KR.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# 🧬 Evolver
|
|
2
2
|
|
|
3
|
-
[](https://github.com/EvoMap/evolver/stargazers)
|
|
4
4
|
[](https://opensource.org/licenses/GPL-3.0)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://github.com/EvoMap/evolver/commits/main)
|
|
7
6
|
[](https://www.npmjs.com/package/@evomap/evolver)
|
|
8
|
-
[](https://github.com/EvoMap/evolver/issues)
|
|
9
7
|
[](https://arxiv.org/abs/2604.15097)
|
|
10
8
|
|
|
11
9
|

|
package/README.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# 🧬 Evolver
|
|
2
2
|
|
|
3
|
-
[](https://github.com/EvoMap/evolver/stargazers)
|
|
4
4
|
[](https://opensource.org/licenses/GPL-3.0)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://github.com/EvoMap/evolver/commits/main)
|
|
7
6
|
[](https://www.npmjs.com/package/@evomap/evolver)
|
|
8
|
-
[](https://github.com/EvoMap/evolver/issues)
|
|
9
7
|
[](https://arxiv.org/abs/2604.15097)
|
|
10
8
|
|
|
11
9
|

|
package/README.zh-CN.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# 🧬 Evolver
|
|
2
2
|
|
|
3
|
-
[](https://github.com/EvoMap/evolver/stargazers)
|
|
4
4
|
[](https://opensource.org/licenses/GPL-3.0)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://github.com/EvoMap/evolver/commits/main)
|
|
7
6
|
[](https://www.npmjs.com/package/@evomap/evolver)
|
|
8
|
-
[](https://github.com/EvoMap/evolver/issues)
|
|
9
7
|
[](https://arxiv.org/abs/2604.15097)
|
|
10
8
|
|
|
11
9
|

|
|
@@ -240,6 +240,257 @@
|
|
|
240
240
|
"tier": "cheap",
|
|
241
241
|
"reasoning_level": "low"
|
|
242
242
|
}
|
|
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"
|
|
243
494
|
}
|
|
244
495
|
]
|
|
245
496
|
}
|
package/index.js
CHANGED
|
@@ -268,23 +268,17 @@ function getLastSignals(statePath) {
|
|
|
268
268
|
|
|
269
269
|
// Singleton Guard - prevent multiple evolver daemon instances.
|
|
270
270
|
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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');
|
|
288
282
|
|
|
289
283
|
function _writeLockAtomic(lockFile, payload) {
|
|
290
284
|
// Round-6 (§19.8): the previous implementation used tmp + rename, which
|
|
@@ -372,38 +366,11 @@ function _lockPayload() {
|
|
|
372
366
|
});
|
|
373
367
|
}
|
|
374
368
|
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
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.
|
|
392
372
|
let _lockRefreshTimer = null;
|
|
393
373
|
|
|
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
|
-
|
|
407
374
|
// Start refreshing the lock file's mtime so other processes can tell this
|
|
408
375
|
// daemon is alive without trusting a (recyclable) PID. unref'd: it never
|
|
409
376
|
// keeps the event loop open on its own, but fires for as long as the daemon
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver",
|
|
3
|
-
"version": "1.89.
|
|
3
|
+
"version": "1.89.3",
|
|
4
4
|
"description": "A GEP-powered self-evolution engine for AI agents. Features automated log analysis and Genome Evolution Protocol (GEP) for auditable, reusable evolution assets.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// refresh_stars_badge.js -- Refresh the static "Stars" badge count in the
|
|
2
|
+
// public README sources to the live GitHub star count.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists:
|
|
5
|
+
// The README stars badge is intentionally STATIC (a plain shields `badge/`
|
|
6
|
+
// URL, not a dynamic `github/stars` one) so it never renders shields.io's
|
|
7
|
+
// "Unable to select next GitHub token from pool" error when their shared
|
|
8
|
+
// GitHub token pool is rate-limited. The trade-off is that the number goes
|
|
9
|
+
// stale. This script re-stamps it at release time so the badge stays current
|
|
10
|
+
// without ever depending on the GitHub API to *render*.
|
|
11
|
+
//
|
|
12
|
+
// Usage:
|
|
13
|
+
// node scripts/refresh_stars_badge.js # update in place
|
|
14
|
+
// node scripts/refresh_stars_badge.js --dry-run # print, write nothing
|
|
15
|
+
// node scripts/refresh_stars_badge.js --repo=O/N # override repo
|
|
16
|
+
// node scripts/refresh_stars_badge.js --count=12345 # skip fetch (testing)
|
|
17
|
+
//
|
|
18
|
+
// Design notes:
|
|
19
|
+
// - Counts come from the PUBLIC repo (default EvoMap/evolver), not this
|
|
20
|
+
// private dev repo.
|
|
21
|
+
// - Fetch uses the already-authenticated `gh` CLI first (deploy.sh preflight
|
|
22
|
+
// guarantees gh auth), falling back to the unauthenticated GitHub REST API.
|
|
23
|
+
// - A fetch failure is NON-FATAL: we warn and exit 0, leaving the badge as
|
|
24
|
+
// is. A flaky GitHub API must never break a release -- avoiding exactly
|
|
25
|
+
// that fragility is the whole reason the badge is static.
|
|
26
|
+
// - It rewrites every README*.md in the repo root that contains the badge,
|
|
27
|
+
// so new translations are picked up automatically.
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
const https = require('https');
|
|
32
|
+
const { execSync } = require('child_process');
|
|
33
|
+
|
|
34
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
35
|
+
const DEFAULT_REPO = 'EvoMap/evolver';
|
|
36
|
+
|
|
37
|
+
// Matches the value segment of `.../badge/Stars-<value>-<color>...` in a
|
|
38
|
+
// shields static badge URL. The value never contains `-` or `/`. Returns a
|
|
39
|
+
// fresh regex each call so callers never share `lastIndex` state.
|
|
40
|
+
const starsBadgeRe = () => /(img\.shields\.io\/badge\/Stars-)([^-/)]+)(-)/g;
|
|
41
|
+
|
|
42
|
+
// Replace the Stars badge value in `content` with `value`. Pure; returns the
|
|
43
|
+
// rewritten string (unchanged if there is no badge or it already matches).
|
|
44
|
+
function rewriteStarsValue(content, value) {
|
|
45
|
+
return content.replace(starsBadgeRe(), (_m, pre, _old, post) => `${pre}${value}${post}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseArgs(argv) {
|
|
49
|
+
const opts = { dryRun: false, repo: DEFAULT_REPO, count: null };
|
|
50
|
+
for (const arg of argv) {
|
|
51
|
+
if (arg === '--dry-run' || arg === '-n') opts.dryRun = true;
|
|
52
|
+
else if (arg.startsWith('--repo=')) opts.repo = arg.slice('--repo='.length);
|
|
53
|
+
else if (arg.startsWith('--count=')) opts.count = Number(arg.slice('--count='.length));
|
|
54
|
+
}
|
|
55
|
+
return opts;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Format an integer the way shields.io's `metric` text formatter does, so the
|
|
59
|
+
// static badge is visually indistinguishable from a dynamic one:
|
|
60
|
+
// 8336 -> "8.3k", 12345 -> "12k", 1100 -> "1.1k", 999 -> "999".
|
|
61
|
+
function metric(n) {
|
|
62
|
+
n = Number(n);
|
|
63
|
+
if (!Number.isFinite(n) || n < 0) return String(n);
|
|
64
|
+
for (const [suffix, size] of [['G', 1e9], ['M', 1e6], ['k', 1e3]]) {
|
|
65
|
+
if (n >= size) {
|
|
66
|
+
const value = n / size;
|
|
67
|
+
const text = value < 10
|
|
68
|
+
? value.toFixed(1).replace(/\.0$/, '')
|
|
69
|
+
: String(Math.round(value));
|
|
70
|
+
return text + suffix;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return String(Math.round(n));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fetchStarsViaGh(repo) {
|
|
77
|
+
try {
|
|
78
|
+
const out = execSync(`gh api repos/${repo} --jq .stargazers_count`, {
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
81
|
+
}).trim();
|
|
82
|
+
const n = Number(out);
|
|
83
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function fetchStarsViaApi(repo) {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const req = https.get(
|
|
92
|
+
`https://api.github.com/repos/${repo}`,
|
|
93
|
+
{ headers: { 'User-Agent': 'evolver-refresh-stars-badge', Accept: 'application/vnd.github+json' } },
|
|
94
|
+
(res) => {
|
|
95
|
+
if (res.statusCode !== 200) { res.resume(); return resolve(null); }
|
|
96
|
+
let body = '';
|
|
97
|
+
res.on('data', (c) => (body += c));
|
|
98
|
+
res.on('end', () => {
|
|
99
|
+
try {
|
|
100
|
+
const n = Number(JSON.parse(body).stargazers_count);
|
|
101
|
+
resolve(Number.isFinite(n) && n > 0 ? n : null);
|
|
102
|
+
} catch { resolve(null); }
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
req.on('error', () => resolve(null));
|
|
107
|
+
req.setTimeout(10000, () => { req.destroy(); resolve(null); });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function resolveStarCount(opts) {
|
|
112
|
+
if (Number.isFinite(opts.count) && opts.count > 0) return opts.count;
|
|
113
|
+
return fetchStarsViaGh(opts.repo) || (await fetchStarsViaApi(opts.repo));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readmeFilesWithBadge() {
|
|
117
|
+
return fs
|
|
118
|
+
.readdirSync(REPO_ROOT)
|
|
119
|
+
.filter((f) => /^README.*\.md$/.test(f))
|
|
120
|
+
.map((f) => path.join(REPO_ROOT, f))
|
|
121
|
+
.filter((p) => starsBadgeRe().test(fs.readFileSync(p, 'utf8')));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
126
|
+
|
|
127
|
+
const count = await resolveStarCount(opts);
|
|
128
|
+
if (count == null) {
|
|
129
|
+
console.warn('[refresh-stars-badge] could not resolve star count (gh + API both failed); leaving badge unchanged');
|
|
130
|
+
return; // non-fatal
|
|
131
|
+
}
|
|
132
|
+
const value = metric(count);
|
|
133
|
+
console.log(`[refresh-stars-badge] ${opts.repo} = ${count} stars -> "${value}"`);
|
|
134
|
+
|
|
135
|
+
const files = readmeFilesWithBadge();
|
|
136
|
+
if (files.length === 0) {
|
|
137
|
+
console.warn('[refresh-stars-badge] no README*.md with a Stars badge found; nothing to do');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let changed = 0;
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
const before = fs.readFileSync(file, 'utf8');
|
|
144
|
+
const after = rewriteStarsValue(before, value);
|
|
145
|
+
const rel = path.relative(REPO_ROOT, file);
|
|
146
|
+
if (after === before) {
|
|
147
|
+
console.log(` ${rel}: already "${value}"`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (opts.dryRun) {
|
|
151
|
+
console.log(` [dry-run] ${rel}: would set Stars -> "${value}"`);
|
|
152
|
+
} else {
|
|
153
|
+
fs.writeFileSync(file, after);
|
|
154
|
+
console.log(` ${rel}: Stars -> "${value}"`);
|
|
155
|
+
}
|
|
156
|
+
changed++;
|
|
157
|
+
}
|
|
158
|
+
console.log(`[refresh-stars-badge] ${changed} file(s) ${opts.dryRun ? 'would change' : 'updated'}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (require.main === module) {
|
|
162
|
+
main().catch((e) => {
|
|
163
|
+
// Never fail the release over a badge refresh.
|
|
164
|
+
console.warn(`[refresh-stars-badge] unexpected error (ignored): ${e && e.message}`);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = { metric, rewriteStarsValue };
|
|
@@ -168,6 +168,7 @@ function copyHookScripts(destDir, evolverRoot) {
|
|
|
168
168
|
const scripts = [
|
|
169
169
|
'_runtimePaths.js',
|
|
170
170
|
'_memoryFiltering.js',
|
|
171
|
+
'_lockPaths.js',
|
|
171
172
|
'evolver-session-start.js',
|
|
172
173
|
'evolver-signal-detect.js',
|
|
173
174
|
'evolver-session-end.js',
|
|
@@ -255,6 +256,7 @@ function removeHookScripts(hooksDir) {
|
|
|
255
256
|
const scripts = [
|
|
256
257
|
'_runtimePaths.js',
|
|
257
258
|
'_memoryFiltering.js',
|
|
259
|
+
'_lockPaths.js',
|
|
258
260
|
'evolver-session-start.js',
|
|
259
261
|
'evolver-signal-detect.js',
|
|
260
262
|
'evolver-session-end.js',
|