From 2097defd45b9fd101d3dbe4c9f04ae37acf2f4a8 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Thu, 23 Jul 2026 10:49:37 +0000 Subject: [PATCH 01/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index fdcbfa6c..d0b1f044 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,42 +1,42 @@ { - "generated_at": "2026-07-20T14:36:00.707057+00:00", + "generated_at": "2026-07-23T10:49:37.739731+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, "items": [ { "type": "merged_pr", - "title": "Add journey report: Newcomer onboarding experience test", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/525", - "timestamp": "2026-07-20T02:22:26Z", + "title": "docs(lessons): EN batch13 (flock, backoff cap, scout/worker modes)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/571", + "timestamp": "2026-07-23T08:17:23Z", "source": "github" }, { "type": "merged_pr", - "title": "Fix #459: [Benchmark] Implement real LessonReuseBench runner or manual evidence protocol", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/526", - "timestamp": "2026-07-19T17:35:55Z", + "title": "docs(lessons): EN batch12 (SAST logs, atomic write, User-Agent)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/570", + "timestamp": "2026-07-23T08:17:19Z", "source": "github" }, { "type": "merged_pr", - "title": "docs: add 5-minute quickstart section to README", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/524", - "timestamp": "2026-07-19T16:05:30Z", + "title": "docs(lessons): EN batch11 (curl fail-fast, mkdir -p, timeout)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/569", + "timestamp": "2026-07-23T08:17:16Z", "source": "github" }, { "type": "merged_pr", - "title": "docs: add contributor troubleshooting FAQ", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/520", - "timestamp": "2026-07-19T12:05:05Z", + "title": "docs(lessons): EN batch10 (mode 600, healthz, idempotent claims)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/568", + "timestamp": "2026-07-23T08:17:12Z", "source": "github" }, { "type": "merged_pr", - "title": "ci: stop fatal-guard false failure when paths do not match", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/518", - "timestamp": "2026-07-19T12:02:58Z", + "title": "docs(lessons): EN batch9 (JSON validate, jitter, proof folders)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/567", + "timestamp": "2026-07-23T08:17:09Z", "source": "github" }, { From 063b7c286f3ee73900d76086b8d4276cdd1ce6e1 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Thu, 23 Jul 2026 11:21:23 +0000 Subject: [PATCH 02/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index d0b1f044..eb479cf0 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-23T10:49:37.739731+00:00", + "generated_at": "2026-07-23T11:21:23.632719+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 4674d6484f6532e36d323a2d9903fc74dd394b14 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Thu, 23 Jul 2026 14:35:24 +0000 Subject: [PATCH 03/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index eb479cf0..66b65f02 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-23T11:21:23.632719+00:00", + "generated_at": "2026-07-23T14:35:24.487687+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 8c692fbd22accae1cedfd94ad2ca715d6cae7639 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Thu, 23 Jul 2026 16:49:10 +0000 Subject: [PATCH 04/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 66b65f02..4f517f46 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-23T14:35:24.487687+00:00", + "generated_at": "2026-07-23T16:49:09.977648+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From a53b196a3659681f500c73f3cb9cac9b0e9ea73b Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Thu, 23 Jul 2026 19:48:05 +0000 Subject: [PATCH 05/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 4f517f46..c8a358c0 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,23 @@ { - "generated_at": "2026-07-23T16:49:09.977648+00:00", + "generated_at": "2026-07-23T19:48:04.985933+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, "items": [ + { + "type": "merged_pr", + "title": "docs(lessons): EN batch15 (cron streams, JSONL ledger, cookie export)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/573", + "timestamp": "2026-07-23T16:58:31Z", + "source": "github" + }, + { + "type": "merged_pr", + "title": "docs(lessons): EN batch14 (PR checklist, idle exit, multi-lane)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/572", + "timestamp": "2026-07-23T16:58:27Z", + "source": "github" + }, { "type": "merged_pr", "title": "docs(lessons): EN batch13 (flock, backoff cap, scout/worker modes)", @@ -25,20 +39,6 @@ "timestamp": "2026-07-23T08:17:16Z", "source": "github" }, - { - "type": "merged_pr", - "title": "docs(lessons): EN batch10 (mode 600, healthz, idempotent claims)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/568", - "timestamp": "2026-07-23T08:17:12Z", - "source": "github" - }, - { - "type": "merged_pr", - "title": "docs(lessons): EN batch9 (JSON validate, jitter, proof folders)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/567", - "timestamp": "2026-07-23T08:17:09Z", - "source": "github" - }, { "type": "challenge", "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", From 8c0ef3c4efce93c55548c9f1937a106abd575795 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Thu, 23 Jul 2026 22:16:46 +0000 Subject: [PATCH 06/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index c8a358c0..274727cf 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-23T19:48:04.985933+00:00", + "generated_at": "2026-07-23T22:16:46.743483+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 27f7c4988ee23c9a689b7992b8b614413f55d345 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Fri, 24 Jul 2026 03:47:05 +0000 Subject: [PATCH 07/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 274727cf..63b8a15b 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,42 +1,42 @@ { - "generated_at": "2026-07-23T22:16:46.743483+00:00", + "generated_at": "2026-07-24T03:47:05.014928+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, "items": [ { "type": "merged_pr", - "title": "docs(lessons): EN batch15 (cron streams, JSONL ledger, cookie export)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/573", - "timestamp": "2026-07-23T16:58:31Z", + "title": "feat(lesson): add git force-with-lease and detached HEAD recovery lesson (#535)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/538", + "timestamp": "2026-07-24T03:40:46Z", "source": "github" }, { "type": "merged_pr", - "title": "docs(lessons): EN batch14 (PR checklist, idle exit, multi-lane)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/572", - "timestamp": "2026-07-23T16:58:27Z", + "title": "feat(lesson): add CSS z-index stacking context debugging lesson (#535)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/539", + "timestamp": "2026-07-24T03:40:43Z", "source": "github" }, { "type": "merged_pr", - "title": "docs(lessons): EN batch13 (flock, backoff cap, scout/worker modes)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/571", - "timestamp": "2026-07-23T08:17:23Z", + "title": "feat(triage): add feedback auto-classifier engine and test suite", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/579", + "timestamp": "2026-07-24T03:12:10Z", "source": "github" }, { "type": "merged_pr", - "title": "docs(lessons): EN batch12 (SAST logs, atomic write, User-Agent)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/570", - "timestamp": "2026-07-23T08:17:19Z", + "title": "docs(lessons): EN batch15 (cron streams, JSONL ledger, cookie export)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/573", + "timestamp": "2026-07-23T16:58:31Z", "source": "github" }, { "type": "merged_pr", - "title": "docs(lessons): EN batch11 (curl fail-fast, mkdir -p, timeout)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/569", - "timestamp": "2026-07-23T08:17:16Z", + "title": "docs(lessons): EN batch14 (PR checklist, idle exit, multi-lane)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/572", + "timestamp": "2026-07-23T16:58:27Z", "source": "github" }, { From f0cf662bf1ad75c70709437db8673bc423008a62 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Fri, 24 Jul 2026 08:44:42 +0000 Subject: [PATCH 08/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 63b8a15b..721cf9a5 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-24T03:47:05.014928+00:00", + "generated_at": "2026-07-24T08:44:42.214977+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 99032154c70c2c5ece0388ba10b470a2a3c796e5 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Fri, 24 Jul 2026 11:13:58 +0000 Subject: [PATCH 09/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 721cf9a5..3f87b21d 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,16 @@ { - "generated_at": "2026-07-24T08:44:42.214977+00:00", + "generated_at": "2026-07-24T11:13:58.689718+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, "items": [ + { + "type": "merged_pr", + "title": "test(fatal-guard): add crash scenario tests (fixes #581)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/584", + "timestamp": "2026-07-24T10:06:26Z", + "source": "github" + }, { "type": "merged_pr", "title": "feat(lesson): add git force-with-lease and detached HEAD recovery lesson (#535)", @@ -32,13 +39,6 @@ "timestamp": "2026-07-23T16:58:31Z", "source": "github" }, - { - "type": "merged_pr", - "title": "docs(lessons): EN batch14 (PR checklist, idle exit, multi-lane)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/572", - "timestamp": "2026-07-23T16:58:27Z", - "source": "github" - }, { "type": "challenge", "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", From 15899c36d1a736c23b86ae698b345d4eac2660d1 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Fri, 24 Jul 2026 14:15:22 +0000 Subject: [PATCH 10/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 3f87b21d..660f2254 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-24T11:13:58.689718+00:00", + "generated_at": "2026-07-24T14:15:21.928123+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 410fe38880975ced1892343ece3d1b331bd17c20 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Fri, 24 Jul 2026 16:59:06 +0000 Subject: [PATCH 11/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 660f2254..e53cde83 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-24T14:15:21.928123+00:00", + "generated_at": "2026-07-24T16:59:06.597894+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 6128b10c03354d30f19f683bc8cef0569b832b07 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Fri, 24 Jul 2026 19:49:13 +0000 Subject: [PATCH 12/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index e53cde83..f9b0728e 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-24T16:59:06.597894+00:00", + "generated_at": "2026-07-24T19:49:13.521326+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 0a0fc42af4d7968f5ed0503b894566e9c765f724 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Fri, 24 Jul 2026 22:29:35 +0000 Subject: [PATCH 13/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index f9b0728e..c23f0bf6 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-24T19:49:13.521326+00:00", + "generated_at": "2026-07-24T22:29:35.672827+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From f914a3ffcc29c52e18c8e3f39eab2a2a4a6ad4da Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sat, 25 Jul 2026 03:41:42 +0000 Subject: [PATCH 14/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index c23f0bf6..c3405771 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-24T22:29:35.672827+00:00", + "generated_at": "2026-07-25T03:41:42.650876+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 1b3d16e7a751a12a28238c87961184d3714a5582 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sat, 25 Jul 2026 08:25:48 +0000 Subject: [PATCH 15/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 61 ++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index c3405771..7c6c86e9 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,23 @@ { - "generated_at": "2026-07-25T03:41:42.650876+00:00", + "generated_at": "2026-07-25T08:25:48.449357+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", - "item_count": 15, + "item_count": 14, "items": [ + { + "type": "merged_pr", + "title": "feat: add GraphQL API for lesson queries (fixes #316)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/576", + "timestamp": "2026-07-25T06:08:59Z", + "source": "github" + }, + { + "type": "merged_pr", + "title": "docs(lessons): EN batch16 (links.json types, restart, honest cash)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/585", + "timestamp": "2026-07-25T05:48:38Z", + "source": "github" + }, { "type": "merged_pr", "title": "test(fatal-guard): add crash scenario tests (fixes #581)", @@ -25,20 +39,6 @@ "timestamp": "2026-07-24T03:40:43Z", "source": "github" }, - { - "type": "merged_pr", - "title": "feat(triage): add feedback auto-classifier engine and test suite", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/579", - "timestamp": "2026-07-24T03:12:10Z", - "source": "github" - }, - { - "type": "merged_pr", - "title": "docs(lessons): EN batch15 (cron streams, JSONL ledger, cookie export)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/573", - "timestamp": "2026-07-23T16:58:31Z", - "source": "github" - }, { "type": "challenge", "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", @@ -138,36 +138,19 @@ }, { "type": "challenge", - "title": "[Ecosystem] Build VS Code extension for MisakaNet lesson search", - "url": "https://github.com/Ikalus1988/MisakaNet/issues/317", - "labels": [ - "enhancement", - "Ring-3", - "bounty", - "agent-friendly", - "status:competition", - "pool:deep", - "status:needs-design", - "priority:later" - ], - "timestamp": "2026-07-02T16:01:28Z", - "source": "github" - }, - { - "type": "challenge", - "title": "[API] Add GraphQL API for flexible lesson queries", - "url": "https://github.com/Ikalus1988/MisakaNet/issues/316", + "title": "[Lesson] Translate top10 most-viewed Chinese lessons to English", + "url": "https://github.com/Ikalus1988/MisakaNet/issues/309", "labels": [ - "enhancement", - "Ring-3", + "Ring-2", "ready", + "area:lessons", "bounty", "agent-friendly", "status:competition", - "pool:deep", + "pool:quick", "priority:later" ], - "timestamp": "2026-07-02T16:01:25Z", + "timestamp": "2026-07-02T15:59:51Z", "source": "github" } ] From b444d22826f9a1522c5d658ab8c8b52021665326 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sat, 25 Jul 2026 10:46:25 +0000 Subject: [PATCH 16/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 7c6c86e9..3ccb7225 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-25T08:25:48.449357+00:00", + "generated_at": "2026-07-25T10:46:25.524156+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From ed6371a7456d600d4bee5993425ddce16f0c2ed0 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sat, 25 Jul 2026 14:04:30 +0000 Subject: [PATCH 17/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 3ccb7225..aa63c325 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-25T10:46:25.524156+00:00", + "generated_at": "2026-07-25T14:04:30.034522+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From 777bab2f7c0fe86fee001e30b4d4444c2275b2ac Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sat, 25 Jul 2026 16:13:18 +0000 Subject: [PATCH 18/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index aa63c325..cd2c5ab9 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,16 @@ { - "generated_at": "2026-07-25T14:04:30.034522+00:00", + "generated_at": "2026-07-25T16:13:18.144711+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, "items": [ + { + "type": "merged_pr", + "title": "chore(deps-dev): bump postcss from 8.5.15 to 8.5.23 in /web", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/586", + "timestamp": "2026-07-25T16:02:51Z", + "source": "github" + }, { "type": "merged_pr", "title": "feat: add GraphQL API for lesson queries (fixes #316)", @@ -32,13 +39,6 @@ "timestamp": "2026-07-24T03:40:46Z", "source": "github" }, - { - "type": "merged_pr", - "title": "feat(lesson): add CSS z-index stacking context debugging lesson (#535)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/539", - "timestamp": "2026-07-24T03:40:43Z", - "source": "github" - }, { "type": "challenge", "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", From 03063f465cba17cf966ddaaf7cecc64ca1d3ba65 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sat, 25 Jul 2026 19:37:01 +0000 Subject: [PATCH 19/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index cd2c5ab9..fa795875 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-25T16:13:18.144711+00:00", + "generated_at": "2026-07-25T19:37:01.766330+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From faf1c53ad0fe41e07a187feba179c266752749e7 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sat, 25 Jul 2026 22:12:12 +0000 Subject: [PATCH 20/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index fa795875..9e99d7c0 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-25T19:37:01.766330+00:00", + "generated_at": "2026-07-25T22:12:12.340514+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From d01e5257ac01fe65064f252b13afda7fed58acbc Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sun, 26 Jul 2026 03:58:49 +0000 Subject: [PATCH 21/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 9e99d7c0..c0acf808 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-25T22:12:12.340514+00:00", + "generated_at": "2026-07-26T03:58:49.729794+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From 14964a9e911a4f3c18dc60c5fcce61398878b25a Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sun, 26 Jul 2026 08:40:45 +0000 Subject: [PATCH 22/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index c0acf808..7838f53c 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-26T03:58:49.729794+00:00", + "generated_at": "2026-07-26T08:40:45.275649+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From 6b9168f1acb426f24154da1efc518643c07c4dd8 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sun, 26 Jul 2026 10:54:42 +0000 Subject: [PATCH 23/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 7838f53c..cfb25c00 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-26T08:40:45.275649+00:00", + "generated_at": "2026-07-26T10:54:42.833283+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From a629b3353b134f9fcb23109f729de3b3e92141d1 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sun, 26 Jul 2026 14:02:09 +0000 Subject: [PATCH 24/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index cfb25c00..02370b9e 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,16 @@ { - "generated_at": "2026-07-26T10:54:42.833283+00:00", + "generated_at": "2026-07-26T14:02:09.202016+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, "items": [ + { + "type": "merged_pr", + "title": "docs: add Glama MCP server deployment lesson", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/590", + "timestamp": "2026-07-26T13:57:12Z", + "source": "github" + }, { "type": "merged_pr", "title": "chore(deps-dev): bump postcss from 8.5.15 to 8.5.23 in /web", @@ -32,13 +39,6 @@ "timestamp": "2026-07-24T10:06:26Z", "source": "github" }, - { - "type": "merged_pr", - "title": "feat(lesson): add git force-with-lease and detached HEAD recovery lesson (#535)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/538", - "timestamp": "2026-07-24T03:40:46Z", - "source": "github" - }, { "type": "challenge", "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", From 31d0224f2d41fd79a033ef8dd9442d5e6b4649c7 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Sun, 26 Jul 2026 16:15:43 +0000 Subject: [PATCH 25/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 02370b9e..52c8a0d1 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-26T14:02:09.202016+00:00", + "generated_at": "2026-07-26T16:15:42.980705+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, From a6cf1e8bba42bf8388b322d0ad343d5860b63880 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 01:34:22 +0000 Subject: [PATCH 26/50] chore: update leaderboard snapshot [skip ci] Signed-off-by: misakanet-bot --- data/leaderboard.json | 92 +++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/data/leaderboard.json b/data/leaderboard.json index d84cc12f..9529b6c2 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -1,134 +1,150 @@ [ { "login": "zsxh1990", - "score": 22.75 + "score": 24.48 + }, + { + "login": "uncledad96-glitch", + "score": 15.36 }, { "login": "2lll5", - "score": 6.15 + "score": 5.06 }, { "login": "solaris", - "score": 3.44 + "score": 3.05 + }, + { + "login": "lb1192176991-lab", + "score": 1.87 }, { "login": "zeroknowledge0x", - "score": 1.82 + "score": 1.56 }, { "login": "sparshgarg999", - "score": 1.42 + "score": 1.26 + }, + { + "login": "namdamdoi68-oss", + "score": 0.98 + }, + { + "login": "ninghuagui-debug", + "score": 0.94 }, { "login": "yh-liao-07", - "score": 1.0 + "score": 0.93 }, { "login": "abhiavi", - "score": 1.0 + "score": 0.93 }, { "login": "ringotokens-commits", - "score": 0.99 + "score": 0.93 }, { "login": "ivegotahunnitonit", - "score": 0.99 + "score": 0.93 }, { "login": "root", - "score": 0.99 + "score": 0.62 }, { "login": "lushan888", - "score": 0.95 + "score": 0.59 }, { "login": "chiranjeevi7777", - "score": 0.95 + "score": 0.59 }, { "login": "zhangjianming7051", - "score": 0.95 + "score": 0.58 }, { "login": "xkes101116-hub", - "score": 0.94 + "score": 0.58 }, { "login": "foxymantou", - "score": 0.94 + "score": 0.58 }, { "login": "votienduong2208", - "score": 0.65 + "score": 0.56 }, { "login": "syzygys", - "score": 0.59 + "score": 0.53 }, { "login": "jhon12091986", - "score": 0.59 + "score": 0.53 }, { "login": "lovewave02", - "score": 0.58 + "score": 0.52 }, { "login": "gothundercats", - "score": 0.56 + "score": 0.51 }, { "login": "zqleslie", - "score": 0.55 + "score": 0.5 }, { "login": "andrianbalanesq", - "score": 0.52 + "score": 0.48 }, { "login": "cyprerask", - "score": 0.51 - }, - { - "login": "rohitmulani63-ops", - "score": 0.47 - }, - { - "login": "skyjames777", "score": 0.47 }, { "login": "doview1", - "score": 0.33 + "score": 0.29 }, { "login": "suresh chouksey", - "score": 0.33 + "score": 0.28 }, { "login": "iccccccccccccc", - "score": 0.33 + "score": 0.28 }, { "login": "sagarmaurya64-ai", - "score": 0.32 + "score": 0.28 + }, + { + "login": "rohitmulani63-ops", + "score": 0.23 + }, + { + "login": "skyjames777", + "score": 0.23 }, { "login": "pian0", - "score": 0.2 + "score": 0.17 }, { "login": "qi574", - "score": 0.16 + "score": 0.14 }, { "login": "cuongwf1711", - "score": 0.16 + "score": 0.14 }, { "login": "lqkhanh295", - "score": 0.13 + "score": 0.11 } ] \ No newline at end of file From 21421d9522c7d437f93a26c5d6340f9e04076cd9 Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 09:10:06 +0800 Subject: [PATCH 27/50] =?UTF-8?q?fix(scripts):=20two-layer=20lesson=20filt?= =?UTF-8?q?ering=20=E2=80=94=20coarse=20keyword=20+=20LLM=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layer 1 (keyword): reject obvious non-lessons (ads, obituaries, pricing) Layer 2 (LLM): read article content, ask 'is this a reusable lesson?' Examples of LLM rejections: - Product announcements (Claude Opus 5) - Strategy analysis (Who's afraid of Chinese models) - Opinion pieces (If coding has been solved) - Policy advocacy (Kill The Cookie Banner) This prevents fabricating lessons from news articles. Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 554 +++++++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 scripts/heartbeat_lesson_pipeline.py diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py new file mode 100644 index 00000000..6fa2e182 --- /dev/null +++ b/scripts/heartbeat_lesson_pipeline.py @@ -0,0 +1,554 @@ +#!/usr/bin/env python3 +"""Heartbeat Lesson Pipeline — fetch → extract → score → PR. + +Automated daily lesson extraction from HN/Dev.to high-quality posts. +Each lesson must pass quality gate (≥75) before PR creation. + +Usage: + python3 scripts/heartbeat_lesson_pipeline.py # full pipeline + python3 scripts/heartbeat_lesson_pipeline.py --dry-run # preview only + python3 scripts/heartbeat_lesson_pipeline.py --target 5 # target count + python3 scripts/heartbeat_lesson_pipeline.py --sources hn # HN only + python3 scripts/heartbeat_lesson_pipeline.py --threshold 80 # stricter gate +""" +from __future__ import annotations + +import argparse +import json +import os +import re +import subprocess +import sys +import textwrap +import urllib.request +from datetime import datetime, timedelta +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +LESSONS_DIR = REPO / "lessons" / "contrib" +SCORER = REPO / "scripts" / "quality_scorer.py" +DOMAIN_KEYWORDS = { + "security": ["vulnerability", "injection", "exploit", "CVE", "breach", "leak", "attack", "auth"], + "mcp": ["MCP", "model context protocol", "tool server", "tool use"], + "agent": ["agent", "autonomous", "multi-agent", "agentic", "orchestration"], + "devops": ["CI/CD", "deploy", "kubernetes", "docker", "infrastructure", "SRE"], + "llm": ["LLM", "GPT", "Claude", "Gemini", "fine-tune", "RAG", "embedding", "token"], + "python": ["Python", "asyncio", "FastAPI", "Django", "pip", "virtualenv"], + "frontend": ["React", "Vue", "Next.js", "TypeScript", "CSS", "Tailwind"], +} + +# Content type filters — only these produce real lessons +LESSON_WORTHY_PATTERNS = [ + # Incident/postmortem (real event with timeline) + r"(?i)(incident|postmortem|outage|downtime|breach|leak(ed)?|security incident|survival guide)", + # Bug/fix (real code issue) + r"(?i)(bug|fix(ed|es)?|patch|regression|crash|error|exception|segfault|broke|broken|doesn.t work)", + # How-to/tutorial (teaches a procedure) + r"(?i)(how to|tutorial|guide|step[- ]by[- ]step|walkthrough|setup|explained|built .*(?:server|tool|app|system))", + # Lessons learned (explicit experience) + r"(?i)(lessons? learned|what I learned|mistakes?|pitfall|gotcha|heads?[- ]up|what I built|what broke|what.s fixed)", + # Performance/debugging (measurable problem) + r"(?i)(performance|latency|memory leak|OOM|timeout|slow|optimiz(e|ation)|faster|speed up)", + # Show HN with technical depth (not just a product launch) + r"(?i)Show HN:.*(?:built|made|created|open[- ]source|library|tool|framework|server|engine|compiler)", + # Configuration/deployment issue + r"(?i)(config|deploy|migration|upgrade|compat|breaking change|deprecat|didn.t fix|it didn.t)", + # Security vulnerability (concrete, not policy) + r"(?i)(vulnerability|CVE|exploit|injection|token.*leak|secret.*expos|admin.*token|SQL.*inject)", + # MCP/Agent technical content + r"(?i)(MCP server|agent.*stack|tool.*use|context.*protocol|prompt.*inject)", + # "I tried X" experience posts + r"(?i)(I (?:tried|rewrote|replaced|migrated|built|debugged|fixed)|here.s what I)", + # Database/infrastructure lessons + r"(?i)(postgres|redis|kubernetes|docker|nginx|sqlite|database.*guide|database.*lesson)", +] + +# Anti-patterns — these are NOT lessons (news/opinion/announcement) +NOT_LESSON_PATTERNS = [ + r"(?i)^(?:Announcing|Introducing|Launch|Release)", # product announcements + r"(?i)(opinion|editorial|think piece|perspective)", # opinion pieces + r"(?i)(has died|obituary|memorial)", # obituaries + r"(?i)(regulation|policy|government|FCC|EU|congress)", # policy news + r"(?i)(advertise|ad|sponsor|pricing)", # ads/pricing + r"(?i)(competitive with|so ta|state[- ]of[- ]the[- ]art|benchmark)", # benchmark comparisons + r"(?i)(strategy|winning|losing|market|revenue)", # business strategy +] + +# ─── Sources ─────────────────────────────────────────────────────────────── + +def fetch_hn_stories(min_points: int = 100, days: int = 7) -> list[dict]: + """Fetch high-point HN stories from Algolia API.""" + cutoff = int((datetime.utcnow() - timedelta(days=days)).timestamp()) + url = ( + f"https://hn.algolia.com/api/v1/search?" + f"tags=story&hitsPerPage=30" + f"&numericFilters=points>{min_points},created_at_i>{cutoff}" + ) + try: + req = urllib.request.Request(url, headers={"User-Agent": "MisakaNet-Heartbeat/1.0"}) + with urllib.request.urlopen(req, timeout=15) as resp: + data = json.loads(resp.read()) + except Exception as e: + print(f"⚠️ HN API error: {e}", file=sys.stderr) + return [] + + stories = [] + for h in data.get("hits", []): + stories.append({ + "source": "hn", + "id": h["objectID"], + "title": h.get("title", ""), + "url": h.get("url", ""), + "points": h.get("points", 0), + "comments": h.get("num_comments", 0), + "author": h.get("author", ""), + "created_at": h.get("created_at", ""), + "hn_url": f"https://news.ycombinator.com/item?id={h['objectID']}", + }) + return stories + + +def fetch_devto_articles(tag: str = "mcp", days: int = 7, top: int = 7) -> list[dict]: + """Fetch top Dev.to articles.""" + url = f"https://dev.to/api/articles?tag={tag}&top={top}&per_page=20" + try: + req = urllib.request.Request(url, headers={"User-Agent": "MisakaNet-Heartbeat/1.0"}) + with urllib.request.urlopen(req, timeout=15) as resp: + data = json.loads(resp.read()) + except Exception as e: + print(f"⚠️ Dev.to API error: {e}", file=sys.stderr) + return [] + + cutoff = datetime.utcnow() - timedelta(days=days) + articles = [] + for a in data: + pub = a.get("published_at", "") + if pub: + try: + pub_dt = datetime.fromisoformat(pub.replace("Z", "+00:00")) + if pub_dt.replace(tzinfo=None) < cutoff: + continue + except ValueError: + pass + articles.append({ + "source": "devto", + "id": str(a["id"]), + "title": a["title"], + "url": a["url"], + "points": a.get("public_reactions_count", 0), + "comments": a.get("comments_count", 0), + "author": a.get("user", {}).get("username", ""), + "tags": a.get("tag_list", []), + }) + return articles + + +# ─── Scoring & Ranking ──────────────────────────────────────────────────── + +def classify_domain(title: str, url: str = "") -> str: + """Auto-classify domain from title/URL.""" + text = f"{title} {url}".lower() + for domain, keywords in DOMAIN_KEYWORDS.items(): + if any(kw.lower() in text for kw in keywords): + return domain + return "engineering" + + +def is_lesson_worthy(item: dict) -> bool: + """Coarse filter: reject obvious non-lesson content (news/ads/obituaries). + Let borderline cases through — LLM will do the real filtering.""" + title = item.get("title", "") + url = item.get("url", "") + + # Hard reject: these are NEVER lessons + for pattern in NOT_LESSON_PATTERNS: + if re.search(pattern, title): + return False + + # Accept: explicit lesson-worthy patterns + for pattern in LESSON_WORTHY_PATTERNS: + if re.search(pattern, f"{title} {url}"): + return True + + # Accept Dev.to with technical tags + if item.get("source") == "devto": + tech_tags = {"mcp", "agent", "devops", "python", "typescript", "security", "debugging"} + if set(item.get("tags", [])) & tech_tags: + return True + + # Accept HN posts with decent engagement (let LLM decide if it's a lesson) + if item.get("source") == "hn" and item.get("points", 0) >= 200: + return True + + return False + + + +def llm_is_lesson_worthy(candidate: dict, content: str) -> bool: + """Fine filter: ask LLM if article contains a reusable lesson.""" + prompt = ( + "You are a technical content evaluator. Read this article and decide:\n" + "Does it contain a REUSABLE technical lesson that an engineer could apply?\n\n" + "A lesson must have:\n" + "- A specific technical problem (not generic advice)\n" + "- A concrete cause (not vague 'it's hard')\n" + "- An actionable solution or mitigation (not just commentary)\n\n" + "NOT lessons: product announcements, opinion pieces, news, benchmarks, strategy analysis.\n\n" + 'Reply with ONLY a JSON object like: {"is_lesson": true, "reason": "one sentence why"}\n\n' + f"ARTICLE TITLE: {candidate['title']}\n" + f"ARTICLE CONTENT:\n{content[:3000]}" + ) + result = call_llm(prompt, max_tokens=100) + if not result: + return False # conservative: skip if LLM fails + try: + # Extract JSON from response + m = re.search(r"\{[^}]+\}", result) + if m: + data = json.loads(m.group()) + worthy = data.get("is_lesson", False) + reason = data.get("reason", "") + if not worthy: + print(f" 🚫 LLM skip: {reason}") + return worthy + except (json.JSONDecodeError, KeyError): + pass + return False + + +def rank_candidate(item: dict) -> float: + """Weighted score for prioritization.""" + score = 0.0 + score += min(item.get("points", 0), 500) * 0.1 # cap at 50 pts + score += min(item.get("comments", 0), 200) * 0.05 # cap at 10 pts + # Bonus for security/MCP topics + title_lower = item.get("title", "").lower() + if any(kw in title_lower for kw in ["security", "vulnerability", "injection"]): + score += 20 + if "mcp" in title_lower: + score += 15 + if "agent" in title_lower: + score += 10 + # Strong bonus for incident/postmortem patterns + if any(kw in title_lower for kw in ["incident", "postmortem", "outage", "breach", "leak"]): + score += 30 + if any(kw in title_lower for kw in ["how to", "tutorial", "lesson", "pitfall"]): + score += 25 + return score + + +def deduplicate(candidates: list[dict], existing_titles: set[str]) -> list[dict]: + """Remove duplicates by title similarity and already-covered topics.""" + seen = set() + unique = [] + for c in candidates: + # Normalize title for dedup + norm = re.sub(r"[^a-z0-9]", "", c["title"].lower())[:40] + if norm in seen: + continue + # Check against existing lessons + if any(norm[:20] in t for t in existing_titles): + continue + seen.add(norm) + unique.append(c) + return unique + + +# ─── Lesson Generation ──────────────────────────────────────────────────── + +def fetch_article_content(url: str) -> str | None: + """Fetch article text content.""" + try: + req = urllib.request.Request(url, headers={ + "User-Agent": "Mozilla/5.0 (compatible; MisakaNet/1.0)" + }) + with urllib.request.urlopen(req, timeout=15) as resp: + html = resp.read().decode("utf-8", errors="replace") + # Strip HTML + text = re.sub(r"]*>.*?", "", html, flags=re.DOTALL) + text = re.sub(r"]*>.*?", "", text, flags=re.DOTALL) + text = re.sub(r"<[^>]+>", "\n", text) + text = re.sub(r"\n\s*\n", "\n\n", text) + text = re.sub(r" +", " ", text).strip() + return text[:8000] # cap for LLM context + except Exception as e: + print(f"⚠️ Fetch failed for {url}: {e}", file=sys.stderr) + return None + + +def call_llm(prompt: str, max_tokens: int = 4000) -> str | None: + """Call LLM via Anthropic-compatible gateway (uses ANTHROPIC_* env vars).""" + base_url = os.environ.get("ANTHROPIC_BASE_URL") + api_key = os.environ.get("ANTHROPIC_API_KEY") + if not base_url or not api_key: + print("❌ ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY required", file=sys.stderr) + return None + + url = f"{base_url}/v1/messages" + body = json.dumps({ + "model": "ppio/pa/claude-haiku-4-5", + "max_tokens": max_tokens, + "messages": [{"role": "user", "content": prompt}], + }).encode() + req = urllib.request.Request(url, data=body, headers={ + "x-api-key": api_key, + "anthropic-version": "2023-06-01", + "Content-Type": "application/json", + }) + try: + with urllib.request.urlopen(req, timeout=60) as resp: + data = json.loads(resp.read()) + return data.get("content", [{}])[0].get("text", "") + except Exception as e: + print(f"⚠️ LLM call failed: {e}", file=sys.stderr) + return None + + +def generate_lesson_prompt(candidate: dict, content: str) -> str: + """Generate LLM prompt for lesson extraction.""" + return textwrap.dedent(f"""\ + Extract a MisakaNet lesson from this article. Output ONLY the lesson markdown file, nothing else. + + REQUIREMENTS (must follow exactly or quality gate fails): + 1. First line: JSON frontmatter between --- delimiters with these fields: + {{"title": "...", "domain": "...", "tags": [...], "language": "en", "status": "published", + "source": "article_url", "created": "{datetime.now().strftime('%Y-%m-%d')}", "confidence": "0.85"}} + 2. Required sections in this exact order: + - ## Problem (specific scenario, not generic) + - ## Root Cause (technical detail, not vague) + - ## Solution (actionable steps with code/config examples) + - ## Verification (executable commands with expected output) + - ## Notes (generalization to other contexts) + - ## References (source URL + HN discussion if applicable) + 3. Code blocks MUST have language tags (```python, ```sql, ```bash, etc.) + 4. Problem section must describe a CONCRETE scenario (who, what tool, what action, what went wrong) + 5. Solution must have numbered steps with code examples + 6. Verification must have copy-pasteable commands + + SOURCE: {candidate['url']} + TITLE: {candidate['title']} + POINTS: {candidate.get('points', 0)} + + ARTICLE CONTENT: + {content[:6000]} + """) + + +# ─── Quality Gate ────────────────────────────────────────────────────────── + +def run_quality_scorer(lesson_path: Path, threshold: int = 75) -> dict: + """Run quality scorer on a single lesson file.""" + result = subprocess.run( + [sys.executable, str(SCORER), str(lesson_path), "--json"], + capture_output=True, text=True, cwd=str(REPO), + ) + try: + data = json.loads(result.stdout) + lesson = data["lessons"][0] + return { + "score": lesson["score"], + "grade": lesson["grade"], + "pass": lesson["score"] >= threshold, + "breakdown": lesson["breakdown"], + } + except (json.JSONDecodeError, KeyError, IndexError) as e: + return {"score": 0, "grade": "F", "pass": False, "error": str(e)} + + +def save_and_score_lesson(content: str, slug: str, threshold: int = 75) -> dict | None: + """Save lesson to disk and run quality gate. Returns None if fails.""" + path = LESSONS_DIR / f"{slug}.md" + path.write_text(content, encoding="utf-8") + + result = run_quality_scorer(path, threshold) + if result["pass"]: + print(f" ✅ {slug}: {result['score']}/100 ({result['grade']})") + return result + else: + print(f" ❌ {slug}: {result['score']}/100 ({result['grade']}) — below {threshold}") + # Clean up failed lesson + path.unlink(missing_ok=True) + return None + + +# ─── Git Operations ──────────────────────────────────────────────────────── + +def git_operations(lesson_files: list[Path], branch_name: str) -> bool: + """Create branch, commit, push, and create PR.""" + try: + # Create branch + subprocess.run(["git", "fetch", "origin", "main"], cwd=REPO, check=True, capture_output=True) + subprocess.run(["git", "checkout", "-b", branch_name, "origin/main"], cwd=REPO, check=True, capture_output=True) + + # Add files + for f in lesson_files: + subprocess.run(["git", "add", str(f.relative_to(REPO))], cwd=REPO, check=True, capture_output=True) + + # Commit + count = len(lesson_files) + msg = f"feat(lessons): {count} high-quality lessons from heartbeat pipeline\n\n" + msg += "Lessons extracted from HN/Dev.to high-point posts.\n" + msg += f"All passed quality gate (≥75/100).\n\n" + msg += "Signed-off-by: Eric Jia <445655361@qq.com>" + subprocess.run(["git", "commit", "-m", msg], cwd=REPO, check=True, capture_output=True) + + # Push + subprocess.run(["git", "push", "origin", branch_name], cwd=REPO, check=True, capture_output=True) + + # Create PR + pr_body = f"## Heartbeat Lesson Batch\n\n" + pr_body += f"**{count} lessons** extracted from high-point HN/Dev.to posts.\n\n" + pr_body += "### Quality Scores\n\n" + pr_body += "| Lesson | Score | Source |\n|--------|-------|--------|\n" + for f in lesson_files: + pr_body += f"| `{f.stem}` | ✅ ≥75 | auto-extracted |\n" + pr_body += "\n---\n🤖 Auto-generated by heartbeat lesson pipeline" + + title_count = min(count, 10) + pr_title = f"feat(lessons): {title_count} community lessons (heartbeat batch)" + + result = subprocess.run( + ["gh", "pr", "create", "--repo", "Ikalus1988/MisakaNet", + "--head", f"zsxh1990:{branch_name}", "--base", "main", + "--title", pr_title, "--body", pr_body], + capture_output=True, text=True, cwd=REPO, + ) + if result.returncode == 0: + pr_url = result.stdout.strip() + print(f"\n🎉 PR created: {pr_url}") + return True + else: + print(f"❌ PR creation failed: {result.stderr}") + return False + + except subprocess.CalledProcessError as e: + print(f"❌ Git operation failed: {e}") + return False + + +# ─── Main Pipeline ───────────────────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser(description="Heartbeat Lesson Pipeline") + parser.add_argument("--dry-run", action="store_true", help="Preview only, don't create PR") + parser.add_argument("--target", type=int, default=10, help="Target lesson count") + parser.add_argument("--threshold", type=int, default=75, help="Quality gate threshold") + parser.add_argument("--sources", default="hn,devto", help="Comma-separated sources") + parser.add_argument("--min-points", type=int, default=100, help="Min HN points") + parser.add_argument("--days", type=int, default=7, help="Lookback days") + parser.add_argument("--llm-provider", default="mify", help="LLM provider for extraction") + args = parser.parse_args() + + print(f"=== Heartbeat Lesson Pipeline ===") + print(f"Target: {args.target} lessons | Threshold: {args.threshold} | Sources: {args.sources}") + print() + + # 1. Fetch candidates + candidates = [] + if "hn" in args.sources: + print("📡 Fetching HN stories...") + candidates.extend(fetch_hn_stories(args.min_points, args.days)) + if "devto" in args.sources: + print("📡 Fetching Dev.to articles...") + for tag in ["mcp", "agent", "devops", "python"]: + candidates.extend(fetch_devto_articles(tag, args.days)) + + print(f"📊 Raw candidates: {len(candidates)}") + + # 2. Deduplicate & rank + existing = set() + for f in LESSONS_DIR.glob("*.md"): + title_match = re.search(r'"title":\s*"([^"]+)"', f.read_text(errors="replace")) + if title_match: + norm = re.sub(r"[^a-z0-9]", "", title_match.group(1).lower())[:40] + existing.add(norm) + + candidates = deduplicate(candidates, existing) + # Filter for lesson-worthy content (not news/opinion/announcement) + before_filter = len(candidates) + candidates = [c for c in candidates if is_lesson_worthy(c)] + print(f"📊 After lesson-worthiness filter: {len(candidates)} (dropped {before_filter - len(candidates)} news/opinion)") + candidates.sort(key=rank_candidate, reverse=True) + candidates = candidates[:args.target * 3] # fetch 3x to account for quality failures + + print(f"📊 After dedup & rank: {len(candidates)}") + print() + + if args.dry_run: + print("=== DRY RUN — Top candidates ===") + for i, c in enumerate(candidates[:args.target], 1): + domain = classify_domain(c["title"], c.get("url", "")) + print(f" {i}. [{domain}] ⭐{c.get('points',0)} | {c['title'][:60]}") + print(f" {c.get('url','')}") + return + + # 3. Extract & score via LLM + print(f"📝 Extracting lessons via LLM (target={args.target})...") + passed = [] + failed = 0 + for i, candidate in enumerate(candidates[:args.target + 5]): # extra buffer for failures + if len(passed) >= args.target: + break + domain = classify_domain(candidate["title"], candidate.get("url", "")) + url = candidate.get("url", "") + if not url: + print(f" ⏭️ [{domain}] No URL: {candidate['title'][:50]}") + continue + + print(f"\n [{i+1}/{args.target+5}] [{domain}] {candidate['title'][:60]}") + print(f" Fetching {url[:80]}...") + + content = fetch_article_content(url) + if not content or len(content) < 200: + print(f" ⏭️ Content too short or fetch failed") + continue + + # LLM gate: check if article is actually lesson-worthy + if not llm_is_lesson_worthy(candidate, content): + print(f" ⏭️ Not a reusable lesson (news/opinion/announcement)") + continue + + # Generate slug from title + slug = re.sub(r"[^a-z0-9]+", "-", candidate["title"].lower())[:60].strip("-") + prompt = generate_lesson_prompt(candidate, content) + + print(f" Calling LLM...") + lesson_text = call_llm(prompt) + if not lesson_text: + print(f" ❌ LLM returned nothing") + failed += 1 + continue + + # Strip markdown fences if LLM wrapped them + lesson_text = re.sub(r"^```(?:markdown)?\s*\n", "", lesson_text) + lesson_text = re.sub(r"\n```\s*$", "", lesson_text) + + # Save and score + result = save_and_score_lesson(lesson_text, slug, args.threshold) + if result: + passed.append(LESSONS_DIR / f"{slug}.md") + else: + failed += 1 + + # Rate limit — 1 req/sec + if i < args.target + 4: + import time + time.sleep(1) + + # 4. Create PR if lessons passed + print(f"\n{'='*50}") + print(f"Results: {len(passed)} passed, {failed} failed, target was {args.target}") + + if passed and not args.dry_run: + branch = f"feat/heartbeat-lessons-{datetime.now().strftime('%Y%m%d')}" + git_operations(passed, branch) + elif passed and args.dry_run: + print("\nDRY RUN — would create PR with:") + for f in passed: + print(f" ✅ {f.name}") + else: + print("\n❌ No lessons passed quality gate — no PR created") + + +if __name__ == "__main__": + main() From 62bb99a08d925c4fa7bbf4bf1f65a1c3242faa46 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 02:45:59 +0000 Subject: [PATCH 28/50] chore: update leaderboard snapshot [skip ci] Signed-off-by: misakanet-bot --- data/leaderboard.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/leaderboard.json b/data/leaderboard.json index 9529b6c2..72661f4d 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -1,7 +1,7 @@ [ { "login": "zsxh1990", - "score": 24.48 + "score": 24.9 }, { "login": "uncledad96-glitch", @@ -57,11 +57,11 @@ }, { "login": "lushan888", - "score": 0.59 + "score": 0.58 }, { "login": "chiranjeevi7777", - "score": 0.59 + "score": 0.58 }, { "login": "zhangjianming7051", From b97ea66ff47e8b39bb9d8489530913dd724259ad Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 11:00:28 +0800 Subject: [PATCH 29/50] fix(scripts): add keyword search to heartbeat pipeline Adds TECH_KEYWORDS list and fetch_hn_by_keyword() for targeted technical content search (postmortem, prompt injection, memory leak, etc.) Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 55 +++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index 6fa2e182..1c7009e4 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -108,6 +108,56 @@ def fetch_hn_stories(min_points: int = 100, days: int = 7) -> list[dict]: return stories +def fetch_hn_by_keyword(keyword: str, min_points: int = 30, limit: int = 5) -> list[dict]: + """Search HN by keyword for targeted technical content.""" + url = ( + f"https://hn.algolia.com/api/v1/search?" + f"query={urllib.request.quote(keyword)}&tags=story" + f"&hitsPerPage={limit}&numericFilters=points>{min_points}" + ) + try: + req = urllib.request.Request(url, headers={"User-Agent": "MisakaNet-Heartbeat/1.0"}) + with urllib.request.urlopen(req, timeout=15) as resp: + data = json.loads(resp.read()) + except Exception as e: + print(f"⚠️ HN keyword search error for '{keyword}': {e}", file=sys.stderr) + return [] + + stories = [] + for h in data.get("hits", []): + stories.append({ + "source": "hn", + "id": h["objectID"], + "title": h.get("title", ""), + "url": h.get("url", ""), + "points": h.get("points", 0), + "comments": h.get("num_comments", 0), + "author": h.get("author", ""), + "created_at": h.get("created_at", ""), + "hn_url": f"https://news.ycombinator.com/item?id={h['objectID']}", + }) + return stories + + +TECH_KEYWORDS = [ + "postmortem incident", + "prompt injection", + "debugging lesson", + "performance optimization", + "memory leak", + "database migration", + "deploy rollback", + "security vulnerability", + "CI CD broken", + "kubernetes crash", + "MCP server", + "agent architecture", + "Redis cache", + "Postgres tuning", + "Docker networking", +] + + def fetch_devto_articles(tag: str = "mcp", days: int = 7, top: int = 7) -> list[dict]: """Fetch top Dev.to articles.""" url = f"https://dev.to/api/articles?tag={tag}&top={top}&per_page=20" @@ -446,8 +496,11 @@ def main(): # 1. Fetch candidates candidates = [] if "hn" in args.sources: - print("📡 Fetching HN stories...") + print("📡 Fetching HN stories (popularity)...") candidates.extend(fetch_hn_stories(args.min_points, args.days)) + print("📡 Fetching HN stories (keyword search)...") + for kw in TECH_KEYWORDS: + candidates.extend(fetch_hn_by_keyword(kw, min_points=30, limit=3)) if "devto" in args.sources: print("📡 Fetching Dev.to articles...") for tag in ["mcp", "agent", "devops", "python"]: From 2032893a1ae06d2422788521fa50b789f078d27b Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 03:03:24 +0000 Subject: [PATCH 30/50] chore: update leaderboard snapshot [skip ci] Signed-off-by: misakanet-bot --- data/leaderboard.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/leaderboard.json b/data/leaderboard.json index 72661f4d..e07229e5 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -1,7 +1,7 @@ [ { "login": "zsxh1990", - "score": 24.9 + "score": 24.89 }, { "login": "uncledad96-glitch", @@ -29,7 +29,7 @@ }, { "login": "namdamdoi68-oss", - "score": 0.98 + "score": 0.97 }, { "login": "ninghuagui-debug", @@ -121,7 +121,7 @@ }, { "login": "sagarmaurya64-ai", - "score": 0.28 + "score": 0.27 }, { "login": "rohitmulani63-ops", From 1535585624297971f95a6f110a5abaa054fa7c24 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 04:05:33 +0000 Subject: [PATCH 31/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 52c8a0d1..8c82bc6b 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,16 @@ { - "generated_at": "2026-07-26T16:15:42.980705+00:00", + "generated_at": "2026-07-27T04:05:33.461192+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, "items": [ + { + "type": "merged_pr", + "title": "docs: update Glama lesson — add glama.json + introspection pitfalls", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/594", + "timestamp": "2026-07-27T01:57:04Z", + "source": "github" + }, { "type": "merged_pr", "title": "docs: add Glama MCP server deployment lesson", @@ -32,13 +39,6 @@ "timestamp": "2026-07-25T05:48:38Z", "source": "github" }, - { - "type": "merged_pr", - "title": "test(fatal-guard): add crash scenario tests (fixes #581)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/584", - "timestamp": "2026-07-24T10:06:26Z", - "source": "github" - }, { "type": "challenge", "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", From 253aa1374352d2e114db666fab2653ce8b3d9f46 Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 12:36:25 +0800 Subject: [PATCH 32/50] feat(scripts): add --upstream flag to push directly to Ikalus1988/MisakaNet Default: push to origin (fork) + PR to upstream --upstream: push directly to upstream + PR from branch Usage: python3 scripts/heartbeat_lesson_pipeline.py --target 8 --upstream Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 53 ++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index 1c7009e4..78b9c72d 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -423,12 +423,26 @@ def save_and_score_lesson(content: str, slug: str, threshold: int = 75) -> dict # ─── Git Operations ──────────────────────────────────────────────────────── -def git_operations(lesson_files: list[Path], branch_name: str) -> bool: - """Create branch, commit, push, and create PR.""" +def git_operations(lesson_files: list[Path], branch_name: str, push_target: str = "origin") -> bool: + """Create branch, commit, push, and create PR. + + push_target: "origin" (fork, default) or "upstream" (Ikalus1988/MisakaNet) + """ try: + # Ensure upstream remote exists + if push_target == "upstream": + result = subprocess.run(["git", "remote", "get-url", "upstream"], capture_output=True, text=True, cwd=REPO) + if result.returncode != 0: + subprocess.run(["git", "remote", "add", "upstream", "https://github.com/Ikalus1988/MisakaNet.git"], cwd=REPO, check=True, capture_output=True) + fetch_ref = "upstream/main" + push_ref = f"upstream {branch_name}" + else: + fetch_ref = "origin/main" + push_ref = f"origin {branch_name}" + # Create branch - subprocess.run(["git", "fetch", "origin", "main"], cwd=REPO, check=True, capture_output=True) - subprocess.run(["git", "checkout", "-b", branch_name, "origin/main"], cwd=REPO, check=True, capture_output=True) + subprocess.run(["git", "fetch", push_target if push_target == "upstream" else "origin", "main"], cwd=REPO, check=True, capture_output=True) + subprocess.run(["git", "checkout", "-b", branch_name, fetch_ref], cwd=REPO, check=True, capture_output=True) # Add files for f in lesson_files: @@ -443,9 +457,9 @@ def git_operations(lesson_files: list[Path], branch_name: str) -> bool: subprocess.run(["git", "commit", "-m", msg], cwd=REPO, check=True, capture_output=True) # Push - subprocess.run(["git", "push", "origin", branch_name], cwd=REPO, check=True, capture_output=True) + subprocess.run(["git", "push", push_target, branch_name], cwd=REPO, check=True, capture_output=True) - # Create PR + # Create PR (only needed when pushing to fork) pr_body = f"## Heartbeat Lesson Batch\n\n" pr_body += f"**{count} lessons** extracted from high-point HN/Dev.to posts.\n\n" pr_body += "### Quality Scores\n\n" @@ -457,12 +471,22 @@ def git_operations(lesson_files: list[Path], branch_name: str) -> bool: title_count = min(count, 10) pr_title = f"feat(lessons): {title_count} community lessons (heartbeat batch)" - result = subprocess.run( - ["gh", "pr", "create", "--repo", "Ikalus1988/MisakaNet", - "--head", f"zsxh1990:{branch_name}", "--base", "main", - "--title", pr_title, "--body", pr_body], - capture_output=True, text=True, cwd=REPO, - ) + if push_target == "upstream": + # Direct push to upstream — create PR from branch + result = subprocess.run( + ["gh", "pr", "create", "--repo", "Ikalus1988/MisakaNet", + "--head", branch_name, "--base", "main", + "--title", pr_title, "--body", pr_body], + capture_output=True, text=True, cwd=REPO, + ) + else: + # Push to fork — create PR from fork:branch to upstream + result = subprocess.run( + ["gh", "pr", "create", "--repo", "Ikalus1988/MisakaNet", + "--head", f"zsxh1990:{branch_name}", "--base", "main", + "--title", pr_title, "--body", pr_body], + capture_output=True, text=True, cwd=REPO, + ) if result.returncode == 0: pr_url = result.stdout.strip() print(f"\n🎉 PR created: {pr_url}") @@ -486,7 +510,7 @@ def main(): parser.add_argument("--sources", default="hn,devto", help="Comma-separated sources") parser.add_argument("--min-points", type=int, default=100, help="Min HN points") parser.add_argument("--days", type=int, default=7, help="Lookback days") - parser.add_argument("--llm-provider", default="mify", help="LLM provider for extraction") + parser.add_argument("--upstream", action="store_true", help="Push directly to Ikalus1988/MisakaNet (upstream)") args = parser.parse_args() print(f"=== Heartbeat Lesson Pipeline ===") @@ -594,7 +618,8 @@ def main(): if passed and not args.dry_run: branch = f"feat/heartbeat-lessons-{datetime.now().strftime('%Y%m%d')}" - git_operations(passed, branch) + push_target = "upstream" if args.upstream else "origin" + git_operations(passed, branch, push_target) elif passed and args.dry_run: print("\nDRY RUN — would create PR with:") for f in passed: From 2b8ffb5b9262f6dc995db224d89853d1f06cd5d2 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 04:38:42 +0000 Subject: [PATCH 33/50] chore: update leaderboard snapshot [skip ci] Signed-off-by: misakanet-bot --- data/leaderboard.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/leaderboard.json b/data/leaderboard.json index e07229e5..382d440b 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -1,7 +1,7 @@ [ { "login": "zsxh1990", - "score": 24.89 + "score": 24.7 }, { "login": "uncledad96-glitch", @@ -21,7 +21,7 @@ }, { "login": "zeroknowledge0x", - "score": 1.56 + "score": 1.55 }, { "login": "sparshgarg999", @@ -97,7 +97,7 @@ }, { "login": "zqleslie", - "score": 0.5 + "score": 0.49 }, { "login": "andrianbalanesq", From 9914d5c57a0b1ca03cc5a82a30177d75261b6cde Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 15:14:04 +0800 Subject: [PATCH 34/50] docs: 3 new lessons from session findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. restricted-interactions-repos.md — repos that don't accept external PRs 2. glama-introspection-gap.md — build success ≠ tools registered 3. merge-probability-calibration.md — A/B test findings on prediction accuracy Signed-off-by: zsxh1990 <445655361@qq.com> --- lessons/contrib/glama-introspection-gap.md | 60 +++++++++++++++++++ .../contrib/merge-probability-calibration.md | 48 +++++++++++++++ .../contrib/restricted-interactions-repos.md | 54 +++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 lessons/contrib/glama-introspection-gap.md create mode 100644 lessons/contrib/merge-probability-calibration.md create mode 100644 lessons/contrib/restricted-interactions-repos.md diff --git a/lessons/contrib/glama-introspection-gap.md b/lessons/contrib/glama-introspection-gap.md new file mode 100644 index 00000000..cd235adb --- /dev/null +++ b/lessons/contrib/glama-introspection-gap.md @@ -0,0 +1,60 @@ +--- +{ + "title": "Glama Introspection Gap — Build Success ≠ Tools Registered", + "domain": "devops", + "tags": ["glama", "mcp", "introspection", "tools", "registry"], + "status": "published", + "source": "agent_experience", + "created": "2026-07-22", + "confidence": "0.90" +} +--- + +## Problem + +After successfully building an MCP server on Glama, the tools don't appear in the Glama API or dashboard. Build success ≠ tools registered — they are separate async processes. + +## Root Cause + +Glama's pipeline has two distinct steps: +1. **Build** — Docker image creation (synchronous, returns immediately) +2. **Introspection** — Runs the MCP server and calls `tools/list` (async, may take minutes to hours) + +The build step can succeed while introspection fails silently. Common causes: +- MCP server starts but doesn't respond to `tools/list` request +- Server crashes during introspection +- glama.json format issues (tools not detected) +- Network timeout during introspection + +## Detection + +```bash +# Check if tools are registered +curl -s "https://glama.ai/api/mcp/v1/servers/OWNER/REPO" | python3 -c " +import json, sys +data = json.load(sys.stdin) +print(f'Tools: {len(data.get(\"tools\", []))}') +" +``` + +If `tools: 0` but build succeeded, introspection failed. + +## Fix Action + +1. **Wait** — introspection is async, may take hours +2. **Sync Server** — trigger Glama to re-read the repo +3. **Rebuild** — force fresh introspection +4. **Check glama.json** — must be minimal format (`$schema` + `maintainers` only) +5. **Check Dockerfile** — ensure MCP server starts and responds to `initialize` request + +## Key Insight + +**glama.json is NOT for tool definitions.** Glama discovers tools via MCP introspection (calling `tools/list`), not from glama.json. The glama.json should only contain: +```json +{ + "$schema": "https://glama.ai/mcp/schemas/server.json", + "maintainers": ["username"] +} +``` + +Complex tool definitions in glama.json are ignored by Glama's introspection system. diff --git a/lessons/contrib/merge-probability-calibration.md b/lessons/contrib/merge-probability-calibration.md new file mode 100644 index 00000000..ee70661e --- /dev/null +++ b/lessons/contrib/merge-probability-calibration.md @@ -0,0 +1,48 @@ +--- +{ + "title": "Merge Probability Calibration — Honest Estimation vs Overfitting", + "domain": "devops", + "tags": ["prediction", "calibration", "merge-rate", "a-b-test", "coach"], + "status": "published", + "source": "agent_experience", + "created": "2026-07-22", + "confidence": "0.90" +} +--- + +## Problem + +PR coaches that predict merge probability often overfit to training data or give misleadingly precise estimates. A 70% accuracy claim may not generalize to new repos or PR types. + +## Root Cause + +1. **Base rate trap** — Most repos have low external merge rates (20-30%). Predicting "medium risk" for everything gives 70%+ accuracy but zero discrimination. + +2. **Signal confusion** — Signals like "needs_preflight" or "large_repo" are risk markers, not success predictors. A PR can have many negative signals and still merge if the maintainer wants it. + +3. **Content blindness** — Current coaches analyze metadata (title, body, files_changed) but not actual diff content. Two PRs with identical metadata can have completely different merge outcomes. + +## A/B Test Results (445 cases) + +| Metric | Value | +|--------|-------| +| Merged PRs mean probability | 0.32 | +| Closed PRs mean probability | 0.30 | +| Gap | +0.02 | +| Discrimination | YES (but limited) | + +**Conclusion:** Merge probability can't be more accurate than the repo's base merge rate without understanding PR content quality. + +## Fix Action + +1. **Use repo merge rate as base** — the most honest starting point +2. **Only adjust for discriminating signals** — merge_conflict (×0.3), duplicate (×0.1), maintainer_internal (×0.05) +3. **Don't double-count** — signals already affect tier, don't also affect probability +4. **Be transparent** — tell users "this repo has 20% merge rate, your PR is slightly better than average" + +## Prevention + +- Always A/B test predictions against actual outcomes +- Use LORO (Leave-One-Repo-Out) validation to detect overfitting +- Report confidence intervals, not point estimates +- Accept that some uncertainty is irreducible (depends on maintainer mood, timing, etc.) diff --git a/lessons/contrib/restricted-interactions-repos.md b/lessons/contrib/restricted-interactions-repos.md new file mode 100644 index 00000000..d30688a1 --- /dev/null +++ b/lessons/contrib/restricted-interactions-repos.md @@ -0,0 +1,54 @@ +--- +{ + "title": "Restricted Interactions Repos — External PRs Not Accepted", + "domain": "devops", + "tags": ["github", "restricted", "external-pr", "contribution", "gatekeeping"], + "status": "published", + "source": "agent_experience", + "created": "2026-07-22", + "confidence": "0.95" +} +--- + +## Problem + +Some large open-source repos have GitHub "restricted interactions" enabled, which prevents non-collaborators from creating PRs, commenting, or even viewing certain features. Contributors waste time preparing PRs that can never be submitted. + +## Root Cause + +GitHub allows repo admins to restrict interactions to collaborators only. This is separate from the standard fork-and-PR workflow. When enabled: +- Non-collaborators cannot create PRs +- Non-collaborators cannot comment on issues/PRs +- The error message is generic: "Interactions on this repository have been restricted to collaborators only" + +## Detection + +Before preparing a PR, check: +```bash +# Check if you can create a PR +gh pr create --repo org/repo --title "test" --body "test" 2>&1 | grep "restricted" + +# Check if you can comment +gh issue comment 1 --repo org/repo --body "test" 2>&1 | grep "restricted" +``` + +## Known Repos with Restricted Interactions + +- `encode/httpx` — Python HTTP library, no external PRs accepted +- Some Grafana Labs repos — require signed commits + restricted interactions + +## Fix Action + +1. Check repo interaction permissions before investing time +2. If restricted, look for alternative contribution channels (discussions, docs) +3. If no alternatives, move to a different repo + +## Prevention + +Always test repo permissions before preparing a PR: +```bash +# Quick permission check +gh api repos/org/repo --jq '.permissions' +``` + +If `pull` is false or interactions are restricted, don't invest time in PR preparation. From 1830dd924587195576d27b25affe03b6f989d135 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 27 Jul 2026 07:15:06 +0000 Subject: [PATCH 35/50] chore: auto-update lessons.json --- data/lessons.json | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/data/lessons.json b/data/lessons.json index 0e969407..b2a09e25 100644 --- a/data/lessons.json +++ b/data/lessons.json @@ -1945,6 +1945,28 @@ "status": "active", "verified": false }, + { + "id": "glama-introspection-gap", + "title": "Glama Introspection Gap — Build Success ≠ Tools Registered", + "domain": "devops", + "tags": [ + "glama", + "mcp", + "introspection", + "tools", + "registry" + ], + "summary": "After successfully building an MCP server on Glama, the tools don't appear in the Glama API or dashboard. Build success ≠ tools registered — they are separate a…", + "preview": "## Problem\n\nAfter successfully building an MCP server on Glama, the tools don't appear in the Glama API or dashboard. Build success ≠ tools registered — they are separate async processes.\n\n## Root Cause\n\nGlama's pipeline has two distinct steps:\n1. **Build** — Docker image creation (synchronous, returns immediately)\n2. **Introspection** — Runs the MCP server and calls `tools/list` (async, may take minutes to hours)\n\nThe build step can succeed while introspection fails silently. Common causes:\n- MCP server starts but doesn't respond to `tools/list` request\n- Server crashes during introspection\n- glama.json format issues (tools not detected)\n- Network timeout during introspection\n\n## Detection\n\n```bash\n# Check if tools are registered\ncurl -s \"https://glama.ai/api/mcp/v1/servers/OWNER/REPO\" | python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\nprint(f'Tools: {len(data.get(\\\"tools\\\", []))}')\n\"\n```\n\nIf `tools: 0` but build succeeded, introspection failed.\n\n## Fix Action\n\n1. **Wait** — introspection is async, may take hours\n2. **Sync Server** — trigger Glama to re-read the repo\n3. **Rebuild** — force fresh introspection\n4. **Check glama.json** — must be minimal format (`$schema` + `maintainers` only)\n5. **Check Dockerfile** — ensure MCP server starts and responds to `initialize` request\n\n## Key Insight\n\n**glama.json is NOT for tool definitions.** Glama discovers tools via MCP introspection (calling `tools/list`), not from glama.json. The glama.json should only contain:\n```json\n{\n \"$schema\": \"https://glama.ai/mcp/schemas/server.json\",\n \"maintainers\": [\"username\"]\n}\n```\n\nComplex tool definitions in glama.json are ignored by Glama's introspection system.", + "url": "lessons/contrib/glama-introspection-gap.md", + "created": "2026-07-22", + "updated": "", + "validity_period_days": 365, + "environment_version": "", + "confidence": 0.5, + "status": "published", + "verified": false + }, { "id": "gpt-sovits-hubert-16khz", "title": "gpt sovits hubert 16khz", @@ -2649,6 +2671,28 @@ "status": "active", "verified": true }, + { + "id": "merge-probability-calibration", + "title": "Merge Probability Calibration — Honest Estimation vs Overfitting", + "domain": "devops", + "tags": [ + "prediction", + "calibration", + "merge-rate", + "a-b-test", + "coach" + ], + "summary": "PR coaches that predict merge probability often overfit to training data or give misleadingly precise estimates. A 70% accuracy claim may not generalize to new …", + "preview": "## Problem\n\nPR coaches that predict merge probability often overfit to training data or give misleadingly precise estimates. A 70% accuracy claim may not generalize to new repos or PR types.\n\n## Root Cause\n\n1. **Base rate trap** — Most repos have low external merge rates (20-30%). Predicting \"medium risk\" for everything gives 70%+ accuracy but zero discrimination.\n\n2. **Signal confusion** — Signals like \"needs_preflight\" or \"large_repo\" are risk markers, not success predictors. A PR can have many negative signals and still merge if the maintainer wants it.\n\n3. **Content blindness** — Current coaches analyze metadata (title, body, files_changed) but not actual diff content. Two PRs with identical metadata can have completely different merge outcomes.\n\n## A/B Test Results (445 cases)\n\n| Metric | Value |\n|--------|-------|\n| Merged PRs mean probability | 0.32 |\n| Closed PRs mean probability | 0.30 |\n| Gap | +0.02 |\n| Discrimination | YES (but limited) |\n\n**Conclusion:** Merge probability can't be more accurate than the repo's base merge rate without understanding PR content quality.\n\n## Fix Action\n\n1. **Use repo merge rate as base** — the most honest starting point\n2. **Only adjust for discriminating signals** — merge_conflict (×0.3), duplicate (×0.1), maintainer_internal (×0.05)\n3. **Don't double-count** — signals already affect tier, don't also affect probability\n4. **Be transparent** — tell users \"this repo has 20% merge rate, your PR is slightly better than average\"\n\n## Prevention\n\n- Always A/B test predictions against actual outcomes\n- Use LORO (Leave-One-Repo-Out) validation to detect overfitting\n- Report confidence intervals, not point estimates\n- Accept that some uncertainty is irreducible (depends on maintainer mood, timing, etc.)", + "url": "lessons/contrib/merge-probability-calibration.md", + "created": "2026-07-22", + "updated": "", + "validity_period_days": 365, + "environment_version": "", + "confidence": 0.5, + "status": "published", + "verified": false + }, { "id": "misakanet-heal-engine-bootstrap-workflow", "title": "MisakaNet --heal Engine Bootstrap Workflow", @@ -3348,6 +3392,28 @@ "status": "published", "verified": true }, + { + "id": "restricted-interactions-repos", + "title": "Restricted Interactions Repos — External PRs Not Accepted", + "domain": "devops", + "tags": [ + "github", + "restricted", + "external-pr", + "contribution", + "gatekeeping" + ], + "summary": "Some large open-source repos have GitHub \"restricted interactions\" enabled, which prevents non-collaborators from creating PRs, commenting, or even viewing cert…", + "preview": "## Problem\n\nSome large open-source repos have GitHub \"restricted interactions\" enabled, which prevents non-collaborators from creating PRs, commenting, or even viewing certain features. Contributors waste time preparing PRs that can never be submitted.\n\n## Root Cause\n\nGitHub allows repo admins to restrict interactions to collaborators only. This is separate from the standard fork-and-PR workflow. When enabled:\n- Non-collaborators cannot create PRs\n- Non-collaborators cannot comment on issues/PRs\n- The error message is generic: \"Interactions on this repository have been restricted to collaborators only\"\n\n## Detection\n\nBefore preparing a PR, check:\n```bash\n# Check if you can create a PR\ngh pr create --repo org/repo --title \"test\" --body \"test\" 2>&1 | grep \"restricted\"\n\n# Check if you can comment\ngh issue comment 1 --repo org/repo --body \"test\" 2>&1 | grep \"restricted\"\n```\n\n## Known Repos with Restricted Interactions\n\n- `encode/httpx` — Python HTTP library, no external PRs accepted\n- Some Grafana Labs repos — require signed commits + restricted interactions\n\n## Fix Action\n\n1. Check repo interaction permissions before investing time\n2. If restricted, look for alternative contribution channels (discussions, docs)\n3. If no alternatives, move to a different repo\n\n## Prevention\n\nAlways test repo permissions before preparing a PR:\n```bash\n# Quick permission check\ngh api repos/org/repo --jq '.permissions'\n```\n\nIf `pull` is false or interactions are restricted, don't invest time in PR preparation.", + "url": "lessons/contrib/restricted-interactions-repos.md", + "created": "2026-07-22", + "updated": "", + "validity_period_days": 365, + "environment_version": "", + "confidence": 0.5, + "status": "published", + "verified": false + }, { "id": "sag-lite-data-quality-cleaning", "title": "sag-lite-data-quality-cleaning", From 1576eb2848948bab7f192d9f7b77c3359ea67d01 Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 15:21:30 +0800 Subject: [PATCH 36/50] fix(scripts): remove --upstream flag (zsxh1990 lacks Ikalus1988 push access) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default fork→PR workflow is the correct approach: push to zsxh1990 → PR to Ikalus1988 Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index 78b9c72d..bbea365e 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -510,7 +510,6 @@ def main(): parser.add_argument("--sources", default="hn,devto", help="Comma-separated sources") parser.add_argument("--min-points", type=int, default=100, help="Min HN points") parser.add_argument("--days", type=int, default=7, help="Lookback days") - parser.add_argument("--upstream", action="store_true", help="Push directly to Ikalus1988/MisakaNet (upstream)") args = parser.parse_args() print(f"=== Heartbeat Lesson Pipeline ===") @@ -618,7 +617,7 @@ def main(): if passed and not args.dry_run: branch = f"feat/heartbeat-lessons-{datetime.now().strftime('%Y%m%d')}" - push_target = "upstream" if args.upstream else "origin" + push_target = "origin" git_operations(passed, branch, push_target) elif passed and args.dry_run: print("\nDRY RUN — would create PR with:") From 5ca4285fe3a6b5e82ab6f0c2c48da054f38eb669 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 07:21:59 +0000 Subject: [PATCH 37/50] chore: update leaderboard snapshot [skip ci] Signed-off-by: misakanet-bot --- data/leaderboard.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/leaderboard.json b/data/leaderboard.json index 382d440b..b2ce0392 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -1,15 +1,15 @@ [ { "login": "zsxh1990", - "score": 24.7 + "score": 24.68 }, { "login": "uncledad96-glitch", - "score": 15.36 + "score": 15.26 }, { "login": "2lll5", - "score": 5.06 + "score": 5.03 }, { "login": "solaris", @@ -105,7 +105,7 @@ }, { "login": "cyprerask", - "score": 0.47 + "score": 0.46 }, { "login": "doview1", From 74a1f8f389d89ad749eee6443b5ddeb144abd4fa Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 15:33:25 +0800 Subject: [PATCH 38/50] =?UTF-8?q?fix(scripts):=20simplify=20git=20ops=20?= =?UTF-8?q?=E2=80=94=20always=20fork=E2=86=92PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove --upstream flag. Always push to zsxh1990 (fork) + PR to Ikalus1988. Add --force to push to handle branch conflicts from prior runs. Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 59 +++++++++------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index bbea365e..041528f6 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -423,26 +423,12 @@ def save_and_score_lesson(content: str, slug: str, threshold: int = 75) -> dict # ─── Git Operations ──────────────────────────────────────────────────────── -def git_operations(lesson_files: list[Path], branch_name: str, push_target: str = "origin") -> bool: - """Create branch, commit, push, and create PR. - - push_target: "origin" (fork, default) or "upstream" (Ikalus1988/MisakaNet) - """ +def git_operations(lesson_files: list[Path], branch_name: str) -> bool: + """Create branch on fork, commit, push, and create PR to upstream.""" try: - # Ensure upstream remote exists - if push_target == "upstream": - result = subprocess.run(["git", "remote", "get-url", "upstream"], capture_output=True, text=True, cwd=REPO) - if result.returncode != 0: - subprocess.run(["git", "remote", "add", "upstream", "https://github.com/Ikalus1988/MisakaNet.git"], cwd=REPO, check=True, capture_output=True) - fetch_ref = "upstream/main" - push_ref = f"upstream {branch_name}" - else: - fetch_ref = "origin/main" - push_ref = f"origin {branch_name}" - - # Create branch - subprocess.run(["git", "fetch", push_target if push_target == "upstream" else "origin", "main"], cwd=REPO, check=True, capture_output=True) - subprocess.run(["git", "checkout", "-b", branch_name, fetch_ref], cwd=REPO, check=True, capture_output=True) + # Create branch from origin/main (fork) + subprocess.run(["git", "fetch", "origin", "main"], cwd=REPO, check=True, capture_output=True) + subprocess.run(["git", "checkout", "-b", branch_name, "origin/main"], cwd=REPO, check=True, capture_output=True) # Add files for f in lesson_files: @@ -456,10 +442,10 @@ def git_operations(lesson_files: list[Path], branch_name: str, push_target: str msg += "Signed-off-by: Eric Jia <445655361@qq.com>" subprocess.run(["git", "commit", "-m", msg], cwd=REPO, check=True, capture_output=True) - # Push - subprocess.run(["git", "push", push_target, branch_name], cwd=REPO, check=True, capture_output=True) + # Push to fork + subprocess.run(["git", "push", "origin", branch_name, "--force"], cwd=REPO, check=True, capture_output=True) - # Create PR (only needed when pushing to fork) + # Create PR to upstream pr_body = f"## Heartbeat Lesson Batch\n\n" pr_body += f"**{count} lessons** extracted from high-point HN/Dev.to posts.\n\n" pr_body += "### Quality Scores\n\n" @@ -471,28 +457,19 @@ def git_operations(lesson_files: list[Path], branch_name: str, push_target: str title_count = min(count, 10) pr_title = f"feat(lessons): {title_count} community lessons (heartbeat batch)" - if push_target == "upstream": - # Direct push to upstream — create PR from branch - result = subprocess.run( - ["gh", "pr", "create", "--repo", "Ikalus1988/MisakaNet", - "--head", branch_name, "--base", "main", - "--title", pr_title, "--body", pr_body], - capture_output=True, text=True, cwd=REPO, - ) - else: - # Push to fork — create PR from fork:branch to upstream - result = subprocess.run( - ["gh", "pr", "create", "--repo", "Ikalus1988/MisakaNet", - "--head", f"zsxh1990:{branch_name}", "--base", "main", - "--title", pr_title, "--body", pr_body], - capture_output=True, text=True, cwd=REPO, - ) + result = subprocess.run( + ["gh", "pr", "create", "--repo", "Ikalus1988/MisakaNet", + "--head", f"zsxh1990:{branch_name}", "--base", "main", + "--title", pr_title, "--body", pr_body], + capture_output=True, text=True, cwd=REPO, + ) if result.returncode == 0: pr_url = result.stdout.strip() print(f"\n🎉 PR created: {pr_url}") return True else: - print(f"❌ PR creation failed: {result.stderr}") + # PR might already exist — try updating + print(f"⚠️ PR create returned: {result.stderr.strip()}") return False except subprocess.CalledProcessError as e: @@ -617,8 +594,8 @@ def main(): if passed and not args.dry_run: branch = f"feat/heartbeat-lessons-{datetime.now().strftime('%Y%m%d')}" - push_target = "origin" - git_operations(passed, branch, push_target) + # Always push to fork (origin) + PR to upstream + git_operations(passed, branch) elif passed and args.dry_run: print("\nDRY RUN — would create PR with:") for f in passed: From d649fb958d2dca3560f74ad25ae81f2c3c4089c3 Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 16:29:26 +0800 Subject: [PATCH 39/50] =?UTF-8?q?fix(scripts):=20add=20fact-check=20gate?= =?UTF-8?q?=20=E2=80=94=20reject=20fabricated=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three-layer filtering: 1. Keyword: reject obvious non-lessons (ads, obituaries) 2. LLM gate: 'is this a reusable lesson?' 3. Fact-check: compare lesson claims against original article If fact-check finds fabricated claims → reject and continue searching. Also updated extraction prompt with CRITICAL RULE — DO NOT FABRICATE: - If article doesn't provide code → 'not provided in source' - If article doesn't provide verification → 'not specified in source' - NEVER invent code, metrics, or steps Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 80 ++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index 041528f6..e7af21f6 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -354,26 +354,80 @@ def call_llm(prompt: str, max_tokens: int = 4000) -> str | None: return None +FACT_CHECK_PROMPT = """You are a fact-checker. Your ONLY job is to output a JSON object. + +Compare the LESSON below against the ARTICLE. Find any fabricated claims. + +RULES: +- Numbers (sizes, percentages, metrics) must match the article +- Code must be from the article or marked "not provided in source" +- Verification steps must be from the article or "not specified in source" +- If the article doesn't mention something, it's fabricated + +Output ONLY this JSON, nothing else: +{{"pass": true, "issues": []}} +or +{{"pass": false, "issues": ["fabricated claim 1", "fabricated claim 2"]}} + +ARTICLE: +{article} + +LESSON: +{lesson}""" + + +def fact_check_lesson(lesson_text: str, article_content: str) -> tuple[bool, list[str]]: + """Verify lesson claims against original article. Returns (pass, issues).""" + prompt = FACT_CHECK_PROMPT.format( + article=article_content[:4000], + lesson=lesson_text[:3000], + ) + result = call_llm(prompt, max_tokens=300) + if not result: + return False, ["LLM fact-check failed"] + try: + m = re.search(r"\{[^}]+\}", result, re.DOTALL) + if m: + # Handle multi-line JSON + json_str = m.group() + # Fix common JSON issues + json_str = re.sub(r'\n', ' ', json_str) + data = json.loads(json_str) + passed = data.get("pass", False) + issues = data.get("issues", []) + return passed, issues + except (json.JSONDecodeError, KeyError): + pass + return False, ["Fact-check response unparseable"] + + def generate_lesson_prompt(candidate: dict, content: str) -> str: """Generate LLM prompt for lesson extraction.""" return textwrap.dedent(f"""\ Extract a MisakaNet lesson from this article. Output ONLY the lesson markdown file, nothing else. + CRITICAL RULE — DO NOT FABRICATE: + - Every claim MUST come from the original article + - If the article doesn't provide code examples, write "not provided in source" + - If the article doesn't provide verification steps, write "not specified in source" + - If the article doesn't provide specific numbers, write "not specified in source" + - NEVER invent code, metrics, or steps that aren't in the article + REQUIREMENTS (must follow exactly or quality gate fails): 1. First line: JSON frontmatter between --- delimiters with these fields: {{"title": "...", "domain": "...", "tags": [...], "language": "en", "status": "published", "source": "article_url", "created": "{datetime.now().strftime('%Y-%m-%d')}", "confidence": "0.85"}} 2. Required sections in this exact order: - - ## Problem (specific scenario, not generic) - - ## Root Cause (technical detail, not vague) - - ## Solution (actionable steps with code/config examples) - - ## Verification (executable commands with expected output) - - ## Notes (generalization to other contexts) - - ## References (source URL + HN discussion if applicable) + - ## Problem (specific scenario from the article) + - ## Root Cause (technical detail from the article) + - ## Solution (steps from the article, or "not specified in source" if missing) + - ## Verification (from the article, or "not specified in source" if missing) + - ## Notes (generalization from the article) + - ## References (source URL) 3. Code blocks MUST have language tags (```python, ```sql, ```bash, etc.) - 4. Problem section must describe a CONCRETE scenario (who, what tool, what action, what went wrong) - 5. Solution must have numbered steps with code examples - 6. Verification must have copy-pasteable commands + 4. Problem section must describe a CONCRETE scenario from the article + 5. Solution must have numbered steps — use article's own words, don't invent + 6. Verification: if the article doesn't provide this, write "not specified in source" SOURCE: {candidate['url']} TITLE: {candidate['title']} @@ -576,6 +630,14 @@ def main(): lesson_text = re.sub(r"^```(?:markdown)?\s*\n", "", lesson_text) lesson_text = re.sub(r"\n```\s*$", "", lesson_text) + # Fact-check: verify lesson claims against original article + print(f" Fact-checking against source...") + passed_check, issues = fact_check_lesson(lesson_text, content) + if not passed_check: + print(f" 🚫 Fact-check FAILED: {'; '.join(issues[:3])}") + failed += 1 + continue + # Save and score result = save_and_score_lesson(lesson_text, slug, args.threshold) if result: From 432e9e3474ffbcdb5e03545fff0e1ca1d07607e4 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 08:31:49 +0000 Subject: [PATCH 40/50] chore: update leaderboard snapshot [skip ci] Signed-off-by: misakanet-bot --- data/leaderboard.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/leaderboard.json b/data/leaderboard.json index b2ce0392..5719c2f2 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -1,7 +1,7 @@ [ { "login": "zsxh1990", - "score": 24.68 + "score": 24.63 }, { "login": "uncledad96-glitch", @@ -109,7 +109,7 @@ }, { "login": "doview1", - "score": 0.29 + "score": 0.28 }, { "login": "suresh chouksey", From eeb27f33be6b7f3606c59eff3ecefd70caa11e3b Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 17:07:59 +0800 Subject: [PATCH 41/50] fix(scripts): increase candidate buffer to 5x target With fact-check rejecting fabricated content, we need more candidates to reach the target count. Changed from 3x to 5x. Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index e7af21f6..0f56cc01 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -576,7 +576,7 @@ def main(): candidates = [c for c in candidates if is_lesson_worthy(c)] print(f"📊 After lesson-worthiness filter: {len(candidates)} (dropped {before_filter - len(candidates)} news/opinion)") candidates.sort(key=rank_candidate, reverse=True) - candidates = candidates[:args.target * 3] # fetch 3x to account for quality failures + candidates = candidates[:args.target * 5] # fetch 5x to account for quality failures and fact-check rejections print(f"📊 After dedup & rank: {len(candidates)}") print() From 38c63a1a21ed8adb98fe3921f5eec38dc68152d0 Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 17:10:13 +0800 Subject: [PATCH 42/50] fix(scripts): increase content limits for extraction and fact-check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extraction: 6000→12000 chars (see full article details) - Fact-check: 4000→10000 chars (verify against full article) Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index 0f56cc01..d7a7f26b 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -379,7 +379,7 @@ def call_llm(prompt: str, max_tokens: int = 4000) -> str | None: def fact_check_lesson(lesson_text: str, article_content: str) -> tuple[bool, list[str]]: """Verify lesson claims against original article. Returns (pass, issues).""" prompt = FACT_CHECK_PROMPT.format( - article=article_content[:4000], + article=article_content[:10000], lesson=lesson_text[:3000], ) result = call_llm(prompt, max_tokens=300) @@ -434,7 +434,7 @@ def generate_lesson_prompt(candidate: dict, content: str) -> str: POINTS: {candidate.get('points', 0)} ARTICLE CONTENT: - {content[:6000]} + {content[:12000]} """) From 6e2a5c50edd65331ef3e38315f1b42fbbc9bacfa Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Mon, 27 Jul 2026 17:21:22 +0800 Subject: [PATCH 43/50] fix(scripts): fact-check ignores metadata fields Only verify content claims (Problem, Root Cause, Solution, etc.). Ignore metadata fields (created date, confidence, status) that are required by the schema but not in the original article. Signed-off-by: Eric Jia <445655361@qq.com> --- scripts/heartbeat_lesson_pipeline.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/scripts/heartbeat_lesson_pipeline.py b/scripts/heartbeat_lesson_pipeline.py index d7a7f26b..6f1175ea 100644 --- a/scripts/heartbeat_lesson_pipeline.py +++ b/scripts/heartbeat_lesson_pipeline.py @@ -356,18 +356,27 @@ def call_llm(prompt: str, max_tokens: int = 4000) -> str | None: FACT_CHECK_PROMPT = """You are a fact-checker. Your ONLY job is to output a JSON object. -Compare the LESSON below against the ARTICLE. Find any fabricated claims. - -RULES: -- Numbers (sizes, percentages, metrics) must match the article -- Code must be from the article or marked "not provided in source" -- Verification steps must be from the article or "not specified in source" -- If the article doesn't mention something, it's fabricated +Compare the LESSON below against the ARTICLE. Find any fabricated CONTENT claims. + +IGNORE these metadata fields (they are required by the schema and will always be "fabricated"): +- created date (always set to today) +- confidence value (always set by the system) +- status field (always "published") +- source URL (always set from the candidate) + +ONLY check these content claims: +- Problem description: must match the article +- Root Cause: must match the article (or "not specified in source") +- Solution steps: must be from the article (or "not specified in source") +- Verification: must be from the article (or "not specified in source") +- Code examples: must be from the article (or "not provided in source") +- Numbers/metrics: must match the article +- Any specific technical details: must be from the article Output ONLY this JSON, nothing else: {{"pass": true, "issues": []}} or -{{"pass": false, "issues": ["fabricated claim 1", "fabricated claim 2"]}} +{{"pass": false, "issues": ["fabricated content claim 1", "fabricated content claim 2"]}} ARTICLE: {article} From ba6632eb07df25e9cb13ab8123d7bfee42711071 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 09:25:23 +0000 Subject: [PATCH 44/50] chore: update leaderboard snapshot [skip ci] Signed-off-by: misakanet-bot --- data/leaderboard.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/leaderboard.json b/data/leaderboard.json index 5719c2f2..480604e7 100644 --- a/data/leaderboard.json +++ b/data/leaderboard.json @@ -1,7 +1,7 @@ [ { "login": "zsxh1990", - "score": 24.63 + "score": 24.6 }, { "login": "uncledad96-glitch", @@ -9,7 +9,7 @@ }, { "login": "2lll5", - "score": 5.03 + "score": 5.02 }, { "login": "solaris", From 5d4d0256c5b552390cb1b18d624d218ca916a204 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 10:11:17 +0000 Subject: [PATCH 45/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 50 ++++++++++++++++---------------- docs/data/lessons.json | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 25 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 8c82bc6b..db567498 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-27T04:05:33.461192+00:00", + "generated_at": "2026-07-27T10:11:17.500176+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 14, @@ -39,6 +39,30 @@ "timestamp": "2026-07-25T05:48:38Z", "source": "github" }, + { + "type": "lesson", + "title": "Glama Introspection Gap — Build Success ≠ Tools Registered", + "url": "lessons/contrib/glama-introspection-gap.md", + "timestamp": "2026-07-22", + "domain": "devops", + "source": "lessons" + }, + { + "type": "lesson", + "title": "Merge Probability Calibration — Honest Estimation vs Overfitting", + "url": "lessons/contrib/merge-probability-calibration.md", + "timestamp": "2026-07-22", + "domain": "devops", + "source": "lessons" + }, + { + "type": "lesson", + "title": "Restricted Interactions Repos — External PRs Not Accepted", + "url": "lessons/contrib/restricted-interactions-repos.md", + "timestamp": "2026-07-22", + "domain": "devops", + "source": "lessons" + }, { "type": "challenge", "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", @@ -75,30 +99,6 @@ "domain": "mcp", "source": "lessons" }, - { - "type": "lesson", - "title": "Repository Traffic Is Not Lesson Use", - "url": "lessons/contrib/repository-traffic-is-not-lesson-use.md", - "timestamp": "2026-07-17", - "domain": "growth", - "source": "lessons" - }, - { - "type": "lesson", - "title": "When Lessons Are Too Heavy, Use Rescue Cards", - "url": "lessons/contrib/rescue-cards-for-non-github-users.md", - "timestamp": "2026-07-17", - "domain": "ux", - "source": "lessons" - }, - { - "type": "lesson", - "title": "Two Evidence Loops for Failure Lessons", - "url": "lessons/contrib/two-evidence-loops-for-failure-lessons.md", - "timestamp": "2026-07-17", - "domain": "growth", - "source": "lessons" - }, { "type": "challenge", "title": "[Bounty][$0][agent competition] Blind test homepage SAG-Lite lesson search", diff --git a/docs/data/lessons.json b/docs/data/lessons.json index 0e969407..b2a09e25 100644 --- a/docs/data/lessons.json +++ b/docs/data/lessons.json @@ -1945,6 +1945,28 @@ "status": "active", "verified": false }, + { + "id": "glama-introspection-gap", + "title": "Glama Introspection Gap — Build Success ≠ Tools Registered", + "domain": "devops", + "tags": [ + "glama", + "mcp", + "introspection", + "tools", + "registry" + ], + "summary": "After successfully building an MCP server on Glama, the tools don't appear in the Glama API or dashboard. Build success ≠ tools registered — they are separate a…", + "preview": "## Problem\n\nAfter successfully building an MCP server on Glama, the tools don't appear in the Glama API or dashboard. Build success ≠ tools registered — they are separate async processes.\n\n## Root Cause\n\nGlama's pipeline has two distinct steps:\n1. **Build** — Docker image creation (synchronous, returns immediately)\n2. **Introspection** — Runs the MCP server and calls `tools/list` (async, may take minutes to hours)\n\nThe build step can succeed while introspection fails silently. Common causes:\n- MCP server starts but doesn't respond to `tools/list` request\n- Server crashes during introspection\n- glama.json format issues (tools not detected)\n- Network timeout during introspection\n\n## Detection\n\n```bash\n# Check if tools are registered\ncurl -s \"https://glama.ai/api/mcp/v1/servers/OWNER/REPO\" | python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\nprint(f'Tools: {len(data.get(\\\"tools\\\", []))}')\n\"\n```\n\nIf `tools: 0` but build succeeded, introspection failed.\n\n## Fix Action\n\n1. **Wait** — introspection is async, may take hours\n2. **Sync Server** — trigger Glama to re-read the repo\n3. **Rebuild** — force fresh introspection\n4. **Check glama.json** — must be minimal format (`$schema` + `maintainers` only)\n5. **Check Dockerfile** — ensure MCP server starts and responds to `initialize` request\n\n## Key Insight\n\n**glama.json is NOT for tool definitions.** Glama discovers tools via MCP introspection (calling `tools/list`), not from glama.json. The glama.json should only contain:\n```json\n{\n \"$schema\": \"https://glama.ai/mcp/schemas/server.json\",\n \"maintainers\": [\"username\"]\n}\n```\n\nComplex tool definitions in glama.json are ignored by Glama's introspection system.", + "url": "lessons/contrib/glama-introspection-gap.md", + "created": "2026-07-22", + "updated": "", + "validity_period_days": 365, + "environment_version": "", + "confidence": 0.5, + "status": "published", + "verified": false + }, { "id": "gpt-sovits-hubert-16khz", "title": "gpt sovits hubert 16khz", @@ -2649,6 +2671,28 @@ "status": "active", "verified": true }, + { + "id": "merge-probability-calibration", + "title": "Merge Probability Calibration — Honest Estimation vs Overfitting", + "domain": "devops", + "tags": [ + "prediction", + "calibration", + "merge-rate", + "a-b-test", + "coach" + ], + "summary": "PR coaches that predict merge probability often overfit to training data or give misleadingly precise estimates. A 70% accuracy claim may not generalize to new …", + "preview": "## Problem\n\nPR coaches that predict merge probability often overfit to training data or give misleadingly precise estimates. A 70% accuracy claim may not generalize to new repos or PR types.\n\n## Root Cause\n\n1. **Base rate trap** — Most repos have low external merge rates (20-30%). Predicting \"medium risk\" for everything gives 70%+ accuracy but zero discrimination.\n\n2. **Signal confusion** — Signals like \"needs_preflight\" or \"large_repo\" are risk markers, not success predictors. A PR can have many negative signals and still merge if the maintainer wants it.\n\n3. **Content blindness** — Current coaches analyze metadata (title, body, files_changed) but not actual diff content. Two PRs with identical metadata can have completely different merge outcomes.\n\n## A/B Test Results (445 cases)\n\n| Metric | Value |\n|--------|-------|\n| Merged PRs mean probability | 0.32 |\n| Closed PRs mean probability | 0.30 |\n| Gap | +0.02 |\n| Discrimination | YES (but limited) |\n\n**Conclusion:** Merge probability can't be more accurate than the repo's base merge rate without understanding PR content quality.\n\n## Fix Action\n\n1. **Use repo merge rate as base** — the most honest starting point\n2. **Only adjust for discriminating signals** — merge_conflict (×0.3), duplicate (×0.1), maintainer_internal (×0.05)\n3. **Don't double-count** — signals already affect tier, don't also affect probability\n4. **Be transparent** — tell users \"this repo has 20% merge rate, your PR is slightly better than average\"\n\n## Prevention\n\n- Always A/B test predictions against actual outcomes\n- Use LORO (Leave-One-Repo-Out) validation to detect overfitting\n- Report confidence intervals, not point estimates\n- Accept that some uncertainty is irreducible (depends on maintainer mood, timing, etc.)", + "url": "lessons/contrib/merge-probability-calibration.md", + "created": "2026-07-22", + "updated": "", + "validity_period_days": 365, + "environment_version": "", + "confidence": 0.5, + "status": "published", + "verified": false + }, { "id": "misakanet-heal-engine-bootstrap-workflow", "title": "MisakaNet --heal Engine Bootstrap Workflow", @@ -3348,6 +3392,28 @@ "status": "published", "verified": true }, + { + "id": "restricted-interactions-repos", + "title": "Restricted Interactions Repos — External PRs Not Accepted", + "domain": "devops", + "tags": [ + "github", + "restricted", + "external-pr", + "contribution", + "gatekeeping" + ], + "summary": "Some large open-source repos have GitHub \"restricted interactions\" enabled, which prevents non-collaborators from creating PRs, commenting, or even viewing cert…", + "preview": "## Problem\n\nSome large open-source repos have GitHub \"restricted interactions\" enabled, which prevents non-collaborators from creating PRs, commenting, or even viewing certain features. Contributors waste time preparing PRs that can never be submitted.\n\n## Root Cause\n\nGitHub allows repo admins to restrict interactions to collaborators only. This is separate from the standard fork-and-PR workflow. When enabled:\n- Non-collaborators cannot create PRs\n- Non-collaborators cannot comment on issues/PRs\n- The error message is generic: \"Interactions on this repository have been restricted to collaborators only\"\n\n## Detection\n\nBefore preparing a PR, check:\n```bash\n# Check if you can create a PR\ngh pr create --repo org/repo --title \"test\" --body \"test\" 2>&1 | grep \"restricted\"\n\n# Check if you can comment\ngh issue comment 1 --repo org/repo --body \"test\" 2>&1 | grep \"restricted\"\n```\n\n## Known Repos with Restricted Interactions\n\n- `encode/httpx` — Python HTTP library, no external PRs accepted\n- Some Grafana Labs repos — require signed commits + restricted interactions\n\n## Fix Action\n\n1. Check repo interaction permissions before investing time\n2. If restricted, look for alternative contribution channels (discussions, docs)\n3. If no alternatives, move to a different repo\n\n## Prevention\n\nAlways test repo permissions before preparing a PR:\n```bash\n# Quick permission check\ngh api repos/org/repo --jq '.permissions'\n```\n\nIf `pull` is false or interactions are restricted, don't invest time in PR preparation.", + "url": "lessons/contrib/restricted-interactions-repos.md", + "created": "2026-07-22", + "updated": "", + "validity_period_days": 365, + "environment_version": "", + "confidence": 0.5, + "status": "published", + "verified": false + }, { "id": "sag-lite-data-quality-cleaning", "title": "sag-lite-data-quality-cleaning", From 86d06bfc57028dab8b898fbd06b66b092eb1b988 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 15:16:50 +0000 Subject: [PATCH 46/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 124 ++++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index db567498..7e1d9b3c 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,24 @@ { - "generated_at": "2026-07-27T10:11:17.500176+00:00", + "generated_at": "2026-07-27T15:16:50.667620+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", - "item_count": 14, + "item_count": 15, "items": [ + { + "type": "challenge", + "title": "[Search][Bounty] Add search result quality feedback loop to search_knowledge.py", + "url": "https://github.com/Ikalus1988/MisakaNet/issues/604", + "labels": [ + "ready", + "bounty", + "agent-friendly", + "status:competition", + "pool:deep", + "priority:next" + ], + "timestamp": "2026-07-27T13:49:01Z", + "source": "github" + }, { "type": "merged_pr", "title": "docs: update Glama lesson — add glama.json + introspection pitfalls", @@ -11,6 +26,23 @@ "timestamp": "2026-07-27T01:57:04Z", "source": "github" }, + { + "type": "challenge", + "title": "Build demand board: aggregate unsolved failure families into public task-family counts", + "url": "https://github.com/Ikalus1988/MisakaNet/issues/591", + "labels": [ + "enhancement", + "ready", + "bounty", + "agent-friendly", + "status:competition", + "pool:deep", + "status:needs-design", + "priority:next" + ], + "timestamp": "2026-07-26T16:51:21Z", + "source": "github" + }, { "type": "merged_pr", "title": "docs: add Glama MCP server deployment lesson", @@ -25,6 +57,23 @@ "timestamp": "2026-07-25T16:02:51Z", "source": "github" }, + { + "type": "challenge", + "title": "Add curl-first intake endpoint: POST /api/intake for MCP, agent, and sandbox feedback", + "url": "https://github.com/Ikalus1988/MisakaNet/issues/589", + "labels": [ + "enhancement", + "ready", + "bounty", + "agent-friendly", + "status:competition", + "pool:deep", + "status:needs-design", + "priority:now" + ], + "timestamp": "2026-07-25T15:55:06Z", + "source": "github" + }, { "type": "merged_pr", "title": "feat: add GraphQL API for lesson queries (fixes #316)", @@ -39,6 +88,23 @@ "timestamp": "2026-07-25T05:48:38Z", "source": "github" }, + { + "type": "challenge", + "title": "Feedback hub: unify search/email/journey/danmaku intake", + "url": "https://github.com/Ikalus1988/MisakaNet/issues/574", + "labels": [ + "enhancement", + "ready", + "bounty", + "agent-friendly", + "status:competition", + "pool:deep", + "status:needs-design", + "priority:now" + ], + "timestamp": "2026-07-23T10:27:25Z", + "source": "github" + }, { "type": "lesson", "title": "Glama Introspection Gap — Build Success ≠ Tools Registered", @@ -98,60 +164,6 @@ "timestamp": "2026-07-17", "domain": "mcp", "source": "lessons" - }, - { - "type": "challenge", - "title": "[Bounty][$0][agent competition] Blind test homepage SAG-Lite lesson search", - "url": "https://github.com/Ikalus1988/MisakaNet/issues/429", - "labels": [ - "good first issue", - "area:tests", - "needs-ac", - "ready", - "agent-friendly", - "no-credentials", - "zero-bounty", - "has-test", - "status:competition", - "pool:quick", - "priority:later" - ], - "timestamp": "2026-07-09T07:03:41Z", - "source": "github" - }, - { - "type": "challenge", - "title": "[Ecosystem] Build Cursor integration for MisakaNet lessons", - "url": "https://github.com/Ikalus1988/MisakaNet/issues/318", - "labels": [ - "enhancement", - "Ring-3", - "bounty", - "agent-friendly", - "status:competition", - "pool:deep", - "status:needs-design", - "priority:later" - ], - "timestamp": "2026-07-02T16:01:30Z", - "source": "github" - }, - { - "type": "challenge", - "title": "[Lesson] Translate top10 most-viewed Chinese lessons to English", - "url": "https://github.com/Ikalus1988/MisakaNet/issues/309", - "labels": [ - "Ring-2", - "ready", - "area:lessons", - "bounty", - "agent-friendly", - "status:competition", - "pool:quick", - "priority:later" - ], - "timestamp": "2026-07-02T15:59:51Z", - "source": "github" } ] } From 84d5d361587c652a74348783d96be4f8eb4736c8 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 17:11:53 +0000 Subject: [PATCH 47/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 70 ++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 7e1d9b3c..26f3cb3e 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,9 +1,23 @@ { - "generated_at": "2026-07-27T15:16:50.667620+00:00", + "generated_at": "2026-07-27T17:11:53.512235+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, "items": [ + { + "type": "merged_pr", + "title": "[Journey Report] Complete MisakaNet Onboarding Path & Friction Analysis (#510)", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/603", + "timestamp": "2026-07-27T15:54:46Z", + "source": "github" + }, + { + "type": "merged_pr", + "title": "feat(lessons): 3 fact-checked community lessons", + "url": "https://github.com/Ikalus1988/MisakaNet/pull/608", + "timestamp": "2026-07-27T15:54:42Z", + "source": "github" + }, { "type": "challenge", "title": "[Search][Bounty] Add search result quality feedback loop to search_knowledge.py", @@ -74,20 +88,6 @@ "timestamp": "2026-07-25T15:55:06Z", "source": "github" }, - { - "type": "merged_pr", - "title": "feat: add GraphQL API for lesson queries (fixes #316)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/576", - "timestamp": "2026-07-25T06:08:59Z", - "source": "github" - }, - { - "type": "merged_pr", - "title": "docs(lessons): EN batch16 (links.json types, restart, honest cash)", - "url": "https://github.com/Ikalus1988/MisakaNet/pull/585", - "timestamp": "2026-07-25T05:48:38Z", - "source": "github" - }, { "type": "challenge", "title": "Feedback hub: unify search/email/journey/danmaku intake", @@ -129,26 +129,6 @@ "domain": "devops", "source": "lessons" }, - { - "type": "challenge", - "title": "[Journey][Bounty] Test the full MisakaNet onboarding path and report real friction", - "url": "https://github.com/Ikalus1988/MisakaNet/issues/510", - "labels": [ - "good first issue", - "needs-ac", - "ready", - "bounty", - "docs", - "agent-friendly", - "status:competition", - "user-research", - "journey-report", - "pool:quick", - "priority:now" - ], - "timestamp": "2026-07-19T03:14:27Z", - "source": "github" - }, { "type": "lesson", "title": "Bounty Contributors Are Not Always Users", @@ -164,6 +144,26 @@ "timestamp": "2026-07-17", "domain": "mcp", "source": "lessons" + }, + { + "type": "challenge", + "title": "[Bounty][$0][agent competition] Blind test homepage SAG-Lite lesson search", + "url": "https://github.com/Ikalus1988/MisakaNet/issues/429", + "labels": [ + "good first issue", + "area:tests", + "needs-ac", + "ready", + "agent-friendly", + "no-credentials", + "zero-bounty", + "has-test", + "status:competition", + "pool:quick", + "priority:later" + ], + "timestamp": "2026-07-09T07:03:41Z", + "source": "github" } ] } From 5a6d11d6fd68667dd8f33d8cdd95b553d4456554 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 19:57:57 +0000 Subject: [PATCH 48/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 26f3cb3e..3751dc1c 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-27T17:11:53.512235+00:00", + "generated_at": "2026-07-27T19:57:57.663971+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 10c5d56c7d0698370c193fb32d8fea2bef874b25 Mon Sep 17 00:00:00 2001 From: misakanet-bot Date: Mon, 27 Jul 2026 22:28:45 +0000 Subject: [PATCH 49/50] chore(data): sync lessons.json + refresh feed --- docs/data/feed.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/feed.json b/docs/data/feed.json index 3751dc1c..27a9ce86 100644 --- a/docs/data/feed.json +++ b/docs/data/feed.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-07-27T19:57:57.663971+00:00", + "generated_at": "2026-07-27T22:28:45.800458+00:00", "repo": "https://github.com/Ikalus1988/MisakaNet", "site": "https://misakanet.org", "item_count": 15, From 77461bf12308a18dcef4521086e0965fb24b7482 Mon Sep 17 00:00:00 2001 From: zsxh1990 <445655361@qq.com> Date: Tue, 28 Jul 2026 10:03:34 +0800 Subject: [PATCH 50/50] feat(lessons): 1 high-quality lessons from heartbeat pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lessons extracted from HN/Dev.to high-point posts. All passed quality gate (≥75/100). Signed-off-by: Eric Jia <445655361@qq.com> --- ...nd-fixing-ghostty-s-largest-memory-leak.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 lessons/contrib/finding-and-fixing-ghostty-s-largest-memory-leak.md diff --git a/lessons/contrib/finding-and-fixing-ghostty-s-largest-memory-leak.md b/lessons/contrib/finding-and-fixing-ghostty-s-largest-memory-leak.md new file mode 100644 index 00000000..4c7c0b0a --- /dev/null +++ b/lessons/contrib/finding-and-fixing-ghostty-s-largest-memory-leak.md @@ -0,0 +1,43 @@ +--- +{"title": "Finding and Fixing Ghostty's Largest Memory Leak", "domain": "systems_programming", "tags": ["memory_management", "memory_leak", "debugging", "terminal_emulator", "mmap"], "language": "en", "status": "published", "source": "https://mitchellh.com/writing/ghostty-memory-leak-fix", "created": "2026-07-28", "confidence": "0.85"} +--- + +## Problem + +Ghostty users reported the terminal emulator consuming absurd amounts of memory, with one user reporting 37 GB after 10 days of uptime. The leak was present since at least Ghostty 1.0, but only became apparent at scale when popular CLI applications like Claude Code started producing the correct conditions to trigger it. Claude Code's CLI produces multi-codepoint grapheme outputs which force Ghostty to regularly use non-standard memory pages, combined with significant scrollback output on the primary screen. + +## Root Cause + +Ghostty uses a PageList data structure (doubly-linked list of memory pages) to store terminal content. Most pages are standard-sized and allocated from a memory pool using mmap. When lines have many emoji, styles, or hyperlinks, larger non-standard pages are allocated directly with mmap, bypassing the pool. + +During scrollback pruning optimization, when the scrollback limit is reached, Ghostty reuses the oldest page as the newest page by moving it from the front to the back of the list. However, the code always resized the page metadata back to standard size without resizing the underlying memory allocation itself. This caused a metadata/memory desync: the metadata indicated standard size (eligible for pool reuse) but the underlying mmap allocation remained the large non-standard size. When the page was eventually freed, the code saw standard size in metadata, assumed it was part of the pool, and never called munmap on the large non-standard allocation, causing a classic memory leak. + +## Solution + +1. Never reuse non-standard pages during scrollback pruning +2. If a non-standard page is encountered during scrollback pruning, destroy it properly by calling munmap +3. Allocate a fresh standard-sized page from the pool instead +4. The core fix checks if the first page's memory length exceeds standard size and destroys the node if true, then breaks from the prune operation + +The code implementing this check: + +```zig +if (first.data.memory.len > std_size) { + self.destroyNode(first); + break :prune; +} +``` + +## Verification + +not specified in source + +## Notes + +The bug remained hidden for years because non-standard pages are rare by design—the architecture optimizes for standard pages as the common case. Only specific scenarios produce non-standard pages in large quantities. The rise of Claude Code as a popular CLI tool changed this by exercising Ghostty in a way that exposed the long-standing bug. The fix is conceptually simple: refuse to optimize (reuse) non-standard pages, treating them instead as exceptions that should be properly freed rather than recycled. This aligns with the current architectural assumption that standard pages are the common case. + +Additionally, virtual memory tags were added on macOS using the Mach kernel to help identify and debug memory allocations during future debugging scenarios. + +## References + +https://mitchellh.com/writing/ghostty-memory-leak-fix \ No newline at end of file