-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.html
More file actions
602 lines (533 loc) · 72.8 KB
/
index.html
File metadata and controls
602 lines (533 loc) · 72.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
<!DOCTYPE html><html lang="zh-Hans" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>守株阁 - 一切都是守株待兔,如切如磋,如琢如磨。I am just joking.</title><meta name="author" content="magicliang"><meta name="copyright" content="magicliang"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="关于技术以及人生">
<meta property="og:type" content="website">
<meta property="og:title" content="守株阁">
<meta property="og:url" content="https://magicliang.github.io/index.html">
<meta property="og:site_name" content="守株阁">
<meta property="og:description" content="关于技术以及人生">
<meta property="og:locale">
<meta property="og:image" content="https://magicliang.github.io/img/rei.jpeg">
<meta property="article:author" content="magicliang">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://magicliang.github.io/img/rei.jpeg"><script type="application/ld+json">{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "守株阁",
"alternateName": [
"一切都是守株待兔,如切如磋,如琢如磨。I am just joking.",
"magicliang.github.io"
],
"url": "https://magicliang.github.io/"
}</script><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://magicliang.github.io/index.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css?v=5.5.4"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.1.0/css/all.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar@0.1.16/dist/snackbar.min.css" media="print" onload="this.media='all'"><script>
(() => {
const saveToLocal = {
set: (key, value, ttl) => {
if (!ttl) return
const expiry = Date.now() + ttl * 86400000
localStorage.setItem(key, JSON.stringify({ value, expiry }))
},
get: key => {
const itemStr = localStorage.getItem(key)
if (!itemStr) return undefined
const { value, expiry } = JSON.parse(itemStr)
if (Date.now() > expiry) {
localStorage.removeItem(key)
return undefined
}
return value
}
}
window.btf = {
saveToLocal,
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
script.onload = script.onreadystatechange = () => {
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
}
script.onerror = reject
document.head.appendChild(script)
}),
getCSS: (url, id) => new Promise((resolve, reject) => {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
if (id) link.id = id
link.onload = link.onreadystatechange = () => {
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
}
link.onerror = reject
document.head.appendChild(link)
}),
addGlobalFn: (key, fn, name = false, parent = window) => {
if (!false && key.startsWith('pjax')) return
const globalFn = parent.globalFn || {}
globalFn[key] = globalFn[key] || {}
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
parent.globalFn = globalFn
}
}
const activateDarkMode = () => {
document.documentElement.setAttribute('data-theme', 'dark')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', 'undefined')
}
}
const activateLightMode = () => {
document.documentElement.setAttribute('data-theme', 'light')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', 'undefined')
}
}
btf.activateDarkMode = activateDarkMode
btf.activateLightMode = activateLightMode
const theme = saveToLocal.get('theme')
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
const asideStatus = saveToLocal.get('aside-status')
if (asideStatus !== undefined) {
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
}
const detectApple = () => {
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
document.documentElement.classList.add('apple')
}
}
detectApple()
})()
</script><script>const GLOBAL_CONFIG = {
root: '/',
algolia: undefined,
localSearch: undefined,
translate: {"defaultEncoding":1,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
copy: {
success: 'Copy Successful',
error: 'Copy Failed',
noSupport: 'Browser Not Supported'
},
relativeDate: {
homepage: true,
post: true
},
runtime: '',
dateSuffix: {
just: 'Just now',
min: 'minutes ago',
hour: 'hours ago',
day: 'days ago',
month: 'months ago'
},
copyright: {"limitCount":50,"languages":{"author":"Author: magicliang","link":"Link: ","source":"Source: 守株阁","info":"Copyright belongs to the author. For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source."}},
lightbox: 'null',
Snackbar: {"chs_to_cht":"You have switched to Traditional Chinese","cht_to_chs":"You have switched to Simplified Chinese","day_to_night":"You have switched to Dark Mode","night_to_day":"You have switched to Light Mode","bgLight":"#49b1f5","bgDark":"#2d3035","position":"bottom-left"},
infinitegrid: {
js: 'https://cdn.jsdelivr.net/npm/@egjs/infinitegrid@4.13.0/dist/infinitegrid.min.js',
buttonText: 'Load More'
},
isPhotoFigcaption: true,
islazyloadPlugin: false,
isAnchor: false,
percent: {
toc: true,
rightside: false,
},
autoDarkmode: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
title: '守株阁',
isHighlightShrink: false,
isToc: false,
pageType: 'home'
}</script><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="/atom.xml" title="守株阁" type="application/atom+xml">
<link href="https://cdn.bootcss.com/KaTeX/0.11.1/katex.min.css" rel="stylesheet" /></head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><div class="spinner-box"><div class="configure-border-1"><div class="configure-core"></div></div><div class="configure-border-2"><div class="configure-core"></div></div><div class="loading-word">Loading...</div></div></div><script>(()=>{
const $loadingBox = document.getElementById('loading-box')
const $body = document.body
const preloader = {
endLoading: () => {
if ($loadingBox.classList.contains('loaded')) return
$body.style.overflow = ''
$loadingBox.classList.add('loaded')
},
initLoading: () => {
$body.style.overflow = 'hidden'
$loadingBox.classList.remove('loaded')
}
}
preloader.initLoading()
if (document.readyState === 'complete') {
preloader.endLoading()
} else {
window.addEventListener('load', preloader.endLoading)
document.addEventListener('DOMContentLoaded', preloader.endLoading)
// Add timeout protection: force end after 7 seconds
setTimeout(preloader.endLoading, 7000)
}
if (false) {
btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init')
btf.addGlobalFn('pjaxComplete', preloader.endLoading, 'preloader_end')
}
})()</script><div class="page" id="body-wrap"><header class="full_page" id="page-header" style="background-image: url(/img/eva-wallpaper.jpg);"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">守株阁</span></a></span><div id="menus"></div></nav><div id="site-info"><h1 id="site-title">守株阁</h1><div id="site_social_icons"><a class="social-icon" href="https://github.com/magicliang" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="mailto:magicliang@qq.com" target="_blank" title="Email"><i class="fas fa-envelope"></i></a></div></div><div id="scroll-down"><i class="fas fa-angle-down scroll-down-effects"></i></div></header><main class="layout" id="content-inner"><div class="recent-posts nc" id="recent-posts"><div class="recent-post-items"><div class="recent-post-item"><div class="post_cover left"><a href="/2026/04/28/%E5%8D%B0%E5%BA%A6%E6%B0%91%E4%B8%BB%E6%B7%B1%E5%BA%A6%E7%A0%94%E7%A9%B6/" title="印度民主深度研究,一个七十八岁的「不可能」还在运行"><img class="post-bg" src="/img/wall-paper-70.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="印度民主深度研究,一个七十八岁的「不可能」还在运行"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/28/%E5%8D%B0%E5%BA%A6%E6%B0%91%E4%B8%BB%E6%B7%B1%E5%BA%A6%E7%A0%94%E7%A9%B6/" title="印度民主深度研究,一个七十八岁的「不可能」还在运行">印度民主深度研究,一个七十八岁的「不可能」还在运行</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-28T06:30:00.000Z" title="Created 2026-04-28 14:30:00">2026-04-28</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-28T08:45:51.767Z" title="Updated 2026-04-28 16:45:51">2026-04-28</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/%E9%9A%8F%E7%AC%94/">随笔</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%94%BF%E6%B2%BB/">政治</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%8D%B0%E5%BA%A6/">印度</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%B0%91%E4%B8%BB/">民主</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%AF%94%E8%BE%83%E6%94%BF%E6%B2%BB/">比较政治</a></span></div><div class="content">先说一件让我自己也愣住的事。 我在写这篇东西之前,在社交媒体上看到一个说法流传得挺广,大意是,印度每次大选,光因为选举暴力死的人就有两万。 看到这个数字的时候,我的第一反应是「卧槽真狠」,第二反应是「等等,这怎么可能」。印度的人口总数是 14 亿左右,中国全国一年各种原因的非正常死亡加起来也就几十万的量级。一次选举死两万人,就相当于在六周时间里、几百个选区里,打出一场小型战争的伤亡。这个量级要是真的,那印度民主就不是「失灵」,是「持续性内战」。 然后我花了整整一个下午翻 V-Dem、Freedom House、ACLED、印度选举委员会(ECI)、印度内政部、还有 ADR 这帮机构的报告,把从 1984 年开始的历次大选的真实死亡数字一个一个比对了一遍。 结果是,这个「两万人」的说法,基本上是个谣言。 但这个谣言有意思的地方在于,它虽然把数量级夸张了 50 到 1000 倍,它想表达的那件事,其实并不完全是空穴来风。印度民主的确有真正的、触目惊心的病灶,只是这些病灶并不长在「大选死人」这件事上,而是长在更深的地方。 所以这篇文章,我想做一件比较麻烦的事。先把印度民主这套东西从头到...</div></div></div><div class="recent-post-item"><div class="post_cover right"><a href="/2026/04/27/Anthropic-Skill-%E5%8D%8A%E5%B9%B4%E6%BC%94%E5%8C%96%E5%8F%B2/" title="从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程"><img class="post-bg" src="/img/wall-paper-11.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/27/Anthropic-Skill-%E5%8D%8A%E5%B9%B4%E6%BC%94%E5%8C%96%E5%8F%B2/" title="从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程">从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-27T10:30:00.000Z" title="Created 2026-04-27 18:30:00">2026-04-27</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-28T08:37:24.193Z" title="Updated 2026-04-28 16:37:24">2026-04-28</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/AI-%E5%B7%A5%E7%A8%8B/">AI 工程</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Agent/">Agent</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/LLM/">LLM</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Claude/">Claude</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Anthropic/">Anthropic</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Agent-Skills/">Agent Skills</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/skill-creator/">skill-creator</a></span></div><div class="content">为什么需要 Skills 用 Claude Code 或者 Cursor 写一段时间代码,总会碰到同一种场景:模型通用能力很强,但一旦要求按公司内部流程办事,比如按团队的 commit 规范写一条 commit message、按固定模板出一份项目周报、按财务系统的字段填一份报销单,它立刻变回一个刚入职三天的实习生——不会写倒不是主要问题,它只是不知道这边是怎么干这件事的。 Anthropic 2025 年 10 月开始填这个坑,做出来的东西叫 Agent Skills。 时间线 2025 年 10 月 16 日:Skills 诞生 Anthropic 工程博客发了《Equipping agents for the real world with Agent Skills》,署名 Barry Zhang、Keith Lazuka、Mahesh Murag,文末缀了一句 “who all really like folders”。 文章对 Skill 的定义很朴素:一个 Skill 就是一个文件夹,里面有一个 SKILL.md,外加可选的脚本、参考文档和资源文件。SKILL.md ...</div></div></div><div class="recent-post-item"><div class="post_cover left"><a href="/2026/04/27/Karpathy-LLM-%E7%BC%96%E7%A0%81%E7%BC%BA%E9%99%B7%E4%B8%8E%E5%9B%9B%E6%9D%A1%E8%A1%8C%E4%B8%BA%E5%87%86%E5%88%99/" title="Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析"><img class="post-bg" src="/img/wall-paper-111.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/27/Karpathy-LLM-%E7%BC%96%E7%A0%81%E7%BC%BA%E9%99%B7%E4%B8%8E%E5%9B%9B%E6%9D%A1%E8%A1%8C%E4%B8%BA%E5%87%86%E5%88%99/" title="Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析">Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-27T05:30:00.000Z" title="Created 2026-04-27 13:30:00">2026-04-27</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-27T10:09:17.118Z" title="Updated 2026-04-27 18:09:17">2026-04-27</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/AI-%E5%B7%A5%E7%A8%8B/">AI 工程</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/LLM/">LLM</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Claude-Code/">Claude Code</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Skill/">Skill</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83/">编码规范</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Andrej-Karpathy/">Andrej Karpathy</a></span></div><div class="content">LLM 编码的自作主张 长期用 Claude Code、Cursor、Copilot 写代码的人大概都熟悉一种感觉:模型并不是不会写,而是写得太多、太聪明、太自作主张。让它修一个 bug,它顺手把无关代码风格也改了;让它加一个简单功能,它给你抽象出三层接口和一套配置系统;让它重构 X,它没问清楚 X 的边界就开始动手。 Andrej Karpathy 在 X 上发过一段对这类问题的精炼归纳,forrestchang 把它整理成了一个 GitHub 仓库 andrej-karpathy-skills,提供一份 60 多行的 CLAUDE.md,外加 Claude Code Plugin 的安装方式。这个仓库的内容可以同时用于 Claude Code、Cursor 以及任何支持 CLAUDE.md 形态的 LLM 编码工具。 这篇文章想讨论三件事:Karpathy 到底诊断出了哪四个偏差、对应的四条行为准则怎么用、以及一个更实际的问题——这套准则到底应该装成 skill 还是直接写进 CLAUDE.md。 Karpathy 观察到的四个系统性偏差 仓库 README 引用了 Karp...</div></div></div><div class="recent-post-item"><div class="post_cover right"><a href="/2026/04/25/%E5%85%B1%E5%92%8C%E5%85%9A%E6%B4%BE%E7%B3%BB%E4%B8%8E%E7%89%B9%E6%9C%97%E6%99%AE%E7%9A%84%E5%85%9A%E5%86%85%E6%9D%83%E5%8A%9B/" title="共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力"><img class="post-bg" src="/img/wall-paper-170.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/25/%E5%85%B1%E5%92%8C%E5%85%9A%E6%B4%BE%E7%B3%BB%E4%B8%8E%E7%89%B9%E6%9C%97%E6%99%AE%E7%9A%84%E5%85%9A%E5%86%85%E6%9D%83%E5%8A%9B/" title="共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力">共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-24T16:00:00.000Z" title="Created 2026-04-25 00:00:00">2026-04-25</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-25T08:29:05.292Z" title="Updated 2026-04-25 16:29:05">2026-04-25</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/%E5%9B%BD%E9%99%85%E8%A7%82%E5%AF%9F/">国际观察</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/">深度调查</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E7%BE%8E%E5%9B%BD%E6%94%BF%E6%B2%BB/">美国政治</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%85%B1%E5%92%8C%E5%85%9A/">共和党</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/MAGA/">MAGA</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Freedom-Caucus/">Freedom Caucus</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E7%89%B9%E6%9C%97%E6%99%AE/">特朗普</a></span></div><div class="content">一个流行的说法是:共和党已经不是原来那个共和党了,它是"MAGA 党"。 这个说法一半对、一半不对。从 2016 年到 2025 年,共和党在党纲、人事、候选人提名、外交立场上都被特朗普翻了个底朝天,2024 年党代会通过的新党纲整篇读下来像是他本人的竞选演讲。但共和党并没有"合并"成一个 MAGA 整体,它内部的派系其实比民主党还多、还乱。特朗普不是消灭了派系,而是压过了派系,所有派系都必须围绕他重新定义自己的位置。 几个绕不开的问题:共和党现在到底有哪些派系?"MAGA 党"这个说法成立到哪一步?特朗普对党的控制力深到什么程度?那个动不动就上新闻的 Freedom Caucus(自由连线/自由党团)到底是干什么的? 一、先搞清一个常识:美国国会的"党"不是铁板一块 和很多东亚国家的政党不同,美国两党都没有党中央、没有党纪处分,也没有"开除党籍"这回事。国会山上真正在运作的不是"党",而是一堆自愿组成的党团(caucus)。一个议员同时加入三四个 caucu...</div></div></div><div class="recent-post-item"><div class="post_cover left"><a href="/2026/04/24/%E7%BE%8E%E5%9B%BD%E8%AD%A6%E5%AF%9F%E6%9A%B4%E5%8A%9B%E8%87%B4%E6%AD%BB%E4%BA%BA%E6%95%B0%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="美国警察暴力与大规模监禁深度调查"><img class="post-bg" src="/img/wall-paper-18.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="美国警察暴力与大规模监禁深度调查"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/24/%E7%BE%8E%E5%9B%BD%E8%AD%A6%E5%AF%9F%E6%9A%B4%E5%8A%9B%E8%87%B4%E6%AD%BB%E4%BA%BA%E6%95%B0%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="美国警察暴力与大规模监禁深度调查">美国警察暴力与大规模监禁深度调查</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-23T16:00:00.000Z" title="Created 2026-04-24 00:00:00">2026-04-24</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-25T08:04:06.238Z" title="Updated 2026-04-25 16:04:06">2026-04-25</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/%E5%9B%BD%E9%99%85%E8%A7%82%E5%AF%9F/">国际观察</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/">深度调查</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E7%BE%8E%E5%9B%BD/">美国</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E8%AD%A6%E5%AF%9F%E6%9A%B4%E5%8A%9B/">警察暴力</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%A4%A7%E8%A7%84%E6%A8%A1%E7%9B%91%E7%A6%81/">大规模监禁</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%85%AC%E5%85%B1%E5%AE%89%E5%85%A8/">公共安全</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%95%B0%E6%8D%AE%E8%B0%83%E6%9F%A5/">数据调查</a></span></div><div class="content">每当 George Floyd、Tyre Nichols 这样的名字登上头条,一个老问题就会被重新提起:美国警察每年到底打死多少人?其中多少死于枪杀? 这个问题听起来应该有标准答案。美国是世界上数据最透明的国家之一,但联邦政府长期拿不出一份准确的官方数字。真正可靠的数据来自《华盛顿邮报》、《卫报》和一群志愿者搭起来的民间数据库,它们与 CDC 官方统计之间的鸿沟,是这个国家在公共安全治理上最尴尬的一道伤口。 本文用多个独立来源交叉核对,回答两个核心问题:一年死多少?多少死于枪?顺带解释,为什么这个看似简单的数字,在美国是一道几十年没解决的统计难题。 讨论警察暴力绕不开它的另一头——监狱。前端是警察在街头打死公民,后端是国家把公民关到老,两件事共享同一套刑事司法系统、同一套政治激励。所以本文分成两部分:前七节谈警察致死,后九节谈大规模监禁。只写一半,就永远只看到了半张脸。 第一部分 · 警察暴力 一、每年约 1,300 人死于警察之手 根据 Mapping Police Violence(MPV,由 Campaign Zero 维护)的 《2024 Police Violence...</div></div></div><div class="recent-post-item"><div class="post_cover right"><a href="/2026/04/22/%E7%BE%8E%E5%86%9B%E4%B8%9C%E5%8D%97%E4%BA%9A%E6%9C%AA%E7%88%86%E5%BC%B9%E8%8D%AF%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="沉默的杀手:美军在东南亚留下的未爆弹药深度调查"><img class="post-bg" src="/img/wall-paper-86.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="沉默的杀手:美军在东南亚留下的未爆弹药深度调查"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/22/%E7%BE%8E%E5%86%9B%E4%B8%9C%E5%8D%97%E4%BA%9A%E6%9C%AA%E7%88%86%E5%BC%B9%E8%8D%AF%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="沉默的杀手:美军在东南亚留下的未爆弹药深度调查">沉默的杀手:美军在东南亚留下的未爆弹药深度调查</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-21T16:00:00.000Z" title="Created 2026-04-22 00:00:00">2026-04-22</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-22T05:19:18.331Z" title="Updated 2026-04-22 13:19:18">2026-04-22</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/">深度调查</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/">深度调查</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E4%B8%9C%E5%8D%97%E4%BA%9A/">东南亚</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%9C%AA%E7%88%86%E5%BC%B9%E8%8D%AF/">未爆弹药</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%9B%BD%E9%99%85%E4%BA%BA%E9%81%93%E4%B8%BB%E4%B9%89/">国际人道主义</a></span></div><div class="content">半个多世纪前,美军在东南亚投下了人类历史上最密集的炸弹雨。战争早已结束,但炸弹从未停止杀人。 本文基于 Landmine & Cluster Munition Monitor、美国国务院、联合国开发计划署(UNDP)、国际红十字委员会(ICRC)、老挝国家监管局(NRA)等权威机构的公开数据,系统梳理美军在老挝、越南、柬埔寨三国留下的未爆弹药(Unexploded Ordnance, UXO)规模、持续伤亡,以及国际社会的回应。 一、历史背景:人类史上最猛烈的轰炸 1.1 老挝——“地球上被轰炸最多的国家” 1964 年至 1973 年间,美国在老挝发动了一场未经国会授权的秘密战争(Secret War)。根据美国国会记录(Congressional Record, 1975 年 5 月 14 日)和 Legacies of War 的数据: 投弹总量:超过 200 万吨弹药 轰炸任务:580,000 次——相当于每 8 分钟投下一架飞机的炸弹载荷,持续 9 年不间断 集束弹药:投下超过 2.7 亿枚集束子弹药(submunitions),当地人称之为"bo...</div></div></div><div class="recent-post-item"><div class="post_cover left"><a href="/2026/04/22/%E8%8B%B9%E6%9E%9C%E5%8D%B0%E5%BA%A6%E4%BA%A7%E8%83%BD%E8%BD%AC%E7%A7%BB%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="苹果印度产能转移深度调查:一场价值千亿美元的制造业大迁徙"><img class="post-bg" src="/img/wall-paper-176.jpeg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="苹果印度产能转移深度调查:一场价值千亿美元的制造业大迁徙"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/22/%E8%8B%B9%E6%9E%9C%E5%8D%B0%E5%BA%A6%E4%BA%A7%E8%83%BD%E8%BD%AC%E7%A7%BB%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="苹果印度产能转移深度调查:一场价值千亿美元的制造业大迁徙">苹果印度产能转移深度调查:一场价值千亿美元的制造业大迁徙</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-21T16:00:00.000Z" title="Created 2026-04-22 00:00:00">2026-04-22</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-22T02:44:21.802Z" title="Updated 2026-04-22 10:44:21">2026-04-22</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/%E4%BA%A7%E4%B8%9A%E8%A7%82%E5%AF%9F/">产业观察</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/">深度调查</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E8%8B%B9%E6%9E%9C/">苹果</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E4%BE%9B%E5%BA%94%E9%93%BE/">供应链</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%8D%B0%E5%BA%A6%E5%88%B6%E9%80%A0/">印度制造</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%9C%B0%E7%BC%98%E6%94%BF%E6%B2%BB/">地缘政治</a></span></div><div class="content">2025 年 3 月的一个深夜,一架满载 iPhone 的货运包机从印度金奈起飞,直奔美国。这批价值 20 亿美元的手机,是苹果有史以来最大规模的一次印度空运。在特朗普关税大棒即将落下的前夜,库克用一种近乎"抢跑"的方式,向全世界宣告了一个事实:iPhone 的制造版图,正在发生不可逆转的重构。 这不是一个简单的"搬工厂"故事。这是一场涉及地缘政治博弈、供应链重塑、劳动力素质鸿沟、基础设施短板的复杂系统工程。苹果的原计划是什么?印度扩产进度顺利吗?遇到了哪些始料未及的问题?本文试图用数据和事实,还原这场价值千亿美元的制造业大迁徙的全貌。 为什么要离开中国? 要理解苹果的印度战略,首先要理解它为什么要离开中国——或者更准确地说,为什么要降低对中国的依赖。 一场疫情暴露的致命弱点 2022 年底,郑州富士康因疫情封控导致大规模生产中断。这座被称为"iPhone 城"的超级工厂,巅峰期拥有超过 35 万名工人和约 38 条产线,承担着全球 iPhone 产量的半壁江山。当它停摆时,苹果在圣诞旺季遭遇了严重的供货危机。 这一事件...</div></div></div><div class="recent-post-item"><div class="post_cover right"><a href="/2026/04/19/Java%E6%A0%88%E5%B8%A7%E7%9C%81%E7%95%A5%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/" title="Java栈帧省略机制详解:为什么异常堆栈会消失?"><img class="post-bg" src="/img/wall-paper-35.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Java栈帧省略机制详解:为什么异常堆栈会消失?"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/19/Java%E6%A0%88%E5%B8%A7%E7%9C%81%E7%95%A5%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/" title="Java栈帧省略机制详解:为什么异常堆栈会消失?">Java栈帧省略机制详解:为什么异常堆栈会消失?</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-19T15:35:00.000Z" title="Created 2026-04-19 23:35:00">2026-04-19</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-19T15:40:31.518Z" title="Updated 2026-04-19 23:40:31">2026-04-19</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/Java/">Java</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/JVM/">JVM</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Java/">Java</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/">异常处理</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/">性能优化</a></span></div><div class="content">引言:诡异的异常堆栈消失现象 线上服务报错时,你打开日志准备排查问题,却发现异常堆栈信息神秘消失了: 1java.lang.NullPointerException 只有短短一行异常类名,没有完整的堆栈跟踪。你可能会怀疑:是日志框架出问题了?还是被什么拦截器截断了? 其实,这是 JVM 的一个性能优化机制,叫做 OmitStackTraceInFastThrow(快速抛出时省略堆栈跟踪)。从 JDK 5 开始引入,默认启用。 本文将深入剖析这个机制的设计动机、工作原理、触发条件,以及如何正确应对。 一、问题场景:异常堆栈去哪了? 1.1 复现现象 用一段简单的代码就能复现: 123456789101112public class ExceptionOmitDemo { public static void main(String[] args) { String msg = null; for (int i = 0; i < 500000; i++) { try { ...</div></div></div><div class="recent-post-item"><div class="post_cover left"><a href="/2026/04/17/Coding-Agent-%E4%BB%A3%E7%A0%81%E6%A3%80%E7%B4%A2%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%EF%BC%9A%E4%BB%8E-GREP-%E5%88%B0%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/" title="Coding Agent 代码检索技术全景:从 GREP 到知识图谱"><img class="post-bg" src="https://images.unsplash.com/photo-1555949963-aa79dcee981c?w=1600&h=900&fit=crop" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Coding Agent 代码检索技术全景:从 GREP 到知识图谱"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/17/Coding-Agent-%E4%BB%A3%E7%A0%81%E6%A3%80%E7%B4%A2%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%EF%BC%9A%E4%BB%8E-GREP-%E5%88%B0%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/" title="Coding Agent 代码检索技术全景:从 GREP 到知识图谱">Coding Agent 代码检索技术全景:从 GREP 到知识图谱</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-17T06:13:00.000Z" title="Created 2026-04-17 14:13:00">2026-04-17</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-17T06:26:03.658Z" title="Updated 2026-04-17 14:26:03">2026-04-17</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/AI%E5%B7%A5%E7%A8%8B/">AI工程</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/LSP/">LSP</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/RAG/">RAG</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/">知识图谱</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/tree-sitter/">tree-sitter</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Coding-Agent/">Coding Agent</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E4%BB%A3%E7%A0%81%E6%A3%80%E7%B4%A2/">代码检索</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/AST/">AST</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/GREP/">GREP</a></span></div><div class="content">Coding Agent 的核心能力之一是在陌生代码库中快速定位相关代码。无论是修复一个 bug、实现一个新功能,还是回答一个架构问题,Agent 的第一步几乎总是:找到相关的代码在哪里。 这个看似简单的问题,实际上是整个 Agentic Coding 领域最基础也最棘手的挑战。不同的 Coding Agent 给出了截然不同的答案——Claude Code 坚持用 grep,Cursor 构建了基于 Merkle 树的向量索引,Aider 用 PageRank 算法生成 RepoMap,Graphify 则把代码库变成了一张知识图谱。 这些方案之间是什么关系?是同一层次的竞争方案,还是不同层次的互补技术?各家的取舍逻辑是什么?未来的发展方向又在哪里? 本文从技术原理出发,逐层拆解当前主流的代码检索方案,分析它们的设计哲学与工程取舍,最终给出一个统一的分层理解框架。 全景导图 %%{init: {'theme':'base', 'themeVariables': {'primar...</div></div></div><div class="recent-post-item"><div class="post_cover right"><a href="/2026/04/16/Agentic-Coding-%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90/" title="Agentic Coding 深度解析:从架构原理到多 Agent 协作"><img class="post-bg" src="/img/wall-paper-95.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Agentic Coding 深度解析:从架构原理到多 Agent 协作"></a></div><div class="recent-post-info"><a class="article-title" href="/2026/04/16/Agentic-Coding-%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90/" title="Agentic Coding 深度解析:从架构原理到多 Agent 协作">Agentic Coding 深度解析:从架构原理到多 Agent 协作</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">Created</span><time class="post-meta-date-created" datetime="2026-04-16T08:00:00.000Z" title="Created 2026-04-16 16:00:00">2026-04-16</time><span class="article-meta-separator">|</span><i class="fas fa-history"></i><span class="article-meta-label">Updated</span><time class="post-meta-date-updated" datetime="2026-04-25T09:15:24.089Z" title="Updated 2026-04-25 17:15:24">2026-04-25</time></span><span class="article-meta"><span class="article-meta-separator">|</span><i class="fas fa-inbox"></i><a class="article-meta__categories" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a></span><span class="article-meta tags"><span class="article-meta-separator">|</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E6%9E%B6%E6%9E%84/">架构</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Agentic-Coding/">Agentic Coding</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Claude-Code/">Claude Code</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/AI-Agent/">AI Agent</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/OpenCode/">OpenCode</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/Subagent/">Subagent</a><span class="article-meta-link">•</span><i class="fas fa-tag"></i><a class="article-meta__tags" href="/tags/%E5%A4%9A-Agent-%E6%9E%B6%E6%9E%84/">多 Agent 架构</a></span></div><div class="content">AI 编程工具的演进,正在经历一次根本性的范式转变:从"补全光标处的代码",到"自主完成端到端工程任务"。这种转变有一个专有名词——Agentic Coding。 围绕 Coding Agent 的讨论,常见两种极端:将其神化为自主智能体,或将其贬为"不过是提示词工程"。两种判断都失之简单。理解这个转变,需要从三个层面展开:工具层(OpenCode 的能力边界)、框架层(多 Agent 协作编排)、方法论层(如何让 Agent 真正服务于工程流程)。本文从真实的架构出发,拆解 Claude Code、OpenCode 等工具的实现模式,厘清各自的设计取舍,深入探讨子 Agent 的本质与多 Agent 协作的核心问题。 什么是 Agentic Coding 传统 AI 编程助手的工作模式是响应式的:开发者提问,AI 回答;开发者选中代码,AI 补全。人始终是执行者,AI 是辅助工具。 Agentic Coding 的工作模式是自主式的:开发者描述目标,Agent 自主规划步骤、调用工具、执行操作、验证结果,直到任务完成...</div></div></div></div><nav id="pagination"><div class="pagination"><span class="page-number current">1</span><a class="page-number" href="/page/2/#content-inner">2</a><span class="space">…</span><a class="page-number" href="/page/38/#content-inner">38</a><a class="extend next" rel="next" href="/page/2/#content-inner"><i class="fas fa-chevron-right fa-fw"></i></a></div></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info text-center"><div class="avatar-img"><img src="/img/rei.jpeg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">magicliang</div><div class="author-info-description">关于技术以及人生</div><div class="site-data"><a href="/archives/"><div class="headline">Articles</div><div class="length-num">377</div></a><a href="/tags/"><div class="headline">Tags</div><div class="length-num">325</div></a><a href="/categories/"><div class="headline">Categories</div><div class="length-num">24</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/magicliang"><i class="fab fa-github"></i><span>Github</span></a><div class="card-info-social-icons"><a class="social-icon" href="https://github.com/magicliang" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="mailto:magicliang@qq.com" target="_blank" title="Email"><i class="fas fa-envelope"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>Announcement</span></div><div class="announcement_content">人生只是,守株待兔</div></div><div class="sticky_layout"><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>Recent Posts</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/2026/04/28/%E5%8D%B0%E5%BA%A6%E6%B0%91%E4%B8%BB%E6%B7%B1%E5%BA%A6%E7%A0%94%E7%A9%B6/" title="印度民主深度研究,一个七十八岁的「不可能」还在运行"><img src="/img/wall-paper-70.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="印度民主深度研究,一个七十八岁的「不可能」还在运行"/></a><div class="content"><a class="title" href="/2026/04/28/%E5%8D%B0%E5%BA%A6%E6%B0%91%E4%B8%BB%E6%B7%B1%E5%BA%A6%E7%A0%94%E7%A9%B6/" title="印度民主深度研究,一个七十八岁的「不可能」还在运行">印度民主深度研究,一个七十八岁的「不可能」还在运行</a><time datetime="2026-04-28T06:30:00.000Z" title="Created 2026-04-28 14:30:00">2026-04-28</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2026/04/27/Anthropic-Skill-%E5%8D%8A%E5%B9%B4%E6%BC%94%E5%8C%96%E5%8F%B2/" title="从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程"><img src="/img/wall-paper-11.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程"/></a><div class="content"><a class="title" href="/2026/04/27/Anthropic-Skill-%E5%8D%8A%E5%B9%B4%E6%BC%94%E5%8C%96%E5%8F%B2/" title="从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程">从 Skill 到 Skills 2.0:Anthropic 这半年怎么把 Agent Skills 做进软件工程</a><time datetime="2026-04-27T10:30:00.000Z" title="Created 2026-04-27 18:30:00">2026-04-27</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2026/04/27/Karpathy-LLM-%E7%BC%96%E7%A0%81%E7%BC%BA%E9%99%B7%E4%B8%8E%E5%9B%9B%E6%9D%A1%E8%A1%8C%E4%B8%BA%E5%87%86%E5%88%99/" title="Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析"><img src="/img/wall-paper-111.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析"/></a><div class="content"><a class="title" href="/2026/04/27/Karpathy-LLM-%E7%BC%96%E7%A0%81%E7%BC%BA%E9%99%B7%E4%B8%8E%E5%9B%9B%E6%9D%A1%E8%A1%8C%E4%B8%BA%E5%87%86%E5%88%99/" title="Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析">Karpathy 视角下的 LLM 编码缺陷:四条行为准则的深度解析</a><time datetime="2026-04-27T05:30:00.000Z" title="Created 2026-04-27 13:30:00">2026-04-27</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2026/04/25/%E5%85%B1%E5%92%8C%E5%85%9A%E6%B4%BE%E7%B3%BB%E4%B8%8E%E7%89%B9%E6%9C%97%E6%99%AE%E7%9A%84%E5%85%9A%E5%86%85%E6%9D%83%E5%8A%9B/" title="共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力"><img src="/img/wall-paper-170.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力"/></a><div class="content"><a class="title" href="/2026/04/25/%E5%85%B1%E5%92%8C%E5%85%9A%E6%B4%BE%E7%B3%BB%E4%B8%8E%E7%89%B9%E6%9C%97%E6%99%AE%E7%9A%84%E5%85%9A%E5%86%85%E6%9D%83%E5%8A%9B/" title="共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力">共和党还剩几个派系?——MAGA、Freedom Caucus 与特朗普的党内权力</a><time datetime="2026-04-24T16:00:00.000Z" title="Created 2026-04-25 00:00:00">2026-04-25</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2026/04/24/%E7%BE%8E%E5%9B%BD%E8%AD%A6%E5%AF%9F%E6%9A%B4%E5%8A%9B%E8%87%B4%E6%AD%BB%E4%BA%BA%E6%95%B0%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="美国警察暴力与大规模监禁深度调查"><img src="/img/wall-paper-18.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="美国警察暴力与大规模监禁深度调查"/></a><div class="content"><a class="title" href="/2026/04/24/%E7%BE%8E%E5%9B%BD%E8%AD%A6%E5%AF%9F%E6%9A%B4%E5%8A%9B%E8%87%B4%E6%AD%BB%E4%BA%BA%E6%95%B0%E6%B7%B1%E5%BA%A6%E8%B0%83%E6%9F%A5/" title="美国警察暴力与大规模监禁深度调查">美国警察暴力与大规模监禁深度调查</a><time datetime="2026-04-23T16:00:00.000Z" title="Created 2026-04-24 00:00:00">2026-04-24</time></div></div></div></div><div class="card-widget card-categories"><div class="item-headline">
<i class="fas fa-folder-open"></i>
<span>Categories</span>
<a class="card-more-btn" href="/categories/" title="View More">
<i class="fas fa-angle-right"></i></a>
</div>
<ul class="card-category-list" id="aside-cat-list">
<li class="card-category-list-item "><a class="card-category-list-link" href="/categories/AI/"><span class="card-category-list-name">AI</span><span class="card-category-list-count">3</span></a></li><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/AI-%E5%B7%A5%E7%A8%8B/"><span class="card-category-list-name">AI 工程</span><span class="card-category-list-count">2</span></a></li><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/AI%E5%B7%A5%E7%A8%8B/"><span class="card-category-list-name">AI工程</span><span class="card-category-list-count">3</span></a><ul class="card-category-list child"><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/AI%E5%B7%A5%E7%A8%8B/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"><span class="card-category-list-name">开发工具</span><span class="card-category-list-count">1</span></a></li><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/AI%E5%B7%A5%E7%A8%8B/%E7%BB%84%E7%BB%87%E5%8F%98%E9%9D%A9/"><span class="card-category-list-name">组织变革</span><span class="card-category-list-count">1</span></a></li></ul></li><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/Java/"><span class="card-category-list-name">Java</span><span class="card-category-list-count">5</span></a></li><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/Web-Security/"><span class="card-category-list-name">Web Security</span><span class="card-category-list-count">1</span></a></li><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/%E4%BA%A7%E4%B8%9A%E8%A7%82%E5%AF%9F/"><span class="card-category-list-name">产业观察</span><span class="card-category-list-count">1</span></a></li>
</ul></div><div class="card-widget card-tags"><div class="item-headline"><i class="fas fa-tags"></i><span>Tags</span></div><div class="card-tag-cloud"><a href="/tags/IOTA/" style="font-size: 1.1em; color: #999">IOTA</a> <a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size: 1.16em; color: #999b9e">面试</a> <a href="/tags/gradle/" style="font-size: 1.1em; color: #999">gradle</a> <a href="/tags/%E6%94%BF%E6%B2%BB/" style="font-size: 1.16em; color: #999b9e">政治</a> <a href="/tags/%E5%93%B2%E5%AD%A6/" style="font-size: 1.16em; color: #999b9e">哲学</a> <a href="/tags/RAG/" style="font-size: 1.16em; color: #999b9e">RAG</a> <a href="/tags/%E5%A4%9A%E6%99%BA%E8%83%BD%E4%BD%93/" style="font-size: 1.16em; color: #999b9e">多智能体</a> <a href="/tags/%E6%96%87%E5%AD%A6/" style="font-size: 1.33em; color: #99a2af">文学</a> <a href="/tags/%E4%BE%9B%E5%BA%94%E9%93%BE/" style="font-size: 1.1em; color: #999">供应链</a> <a href="/tags/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/" style="font-size: 1.44em; color: #99a7ba">软件工程</a> <a href="/tags/javac/" style="font-size: 1.1em; color: #999">javac</a> <a href="/tags/%E4%BA%91%E8%AE%A1%E7%AE%97/" style="font-size: 1.16em; color: #999b9e">云计算</a> <a href="/tags/%E8%AD%A6%E5%AF%9F%E6%9A%B4%E5%8A%9B/" style="font-size: 1.1em; color: #999">警察暴力</a> <a href="/tags/%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E/" style="font-size: 1.1em; color: #999">存储引擎</a> <a href="/tags/LLM/" style="font-size: 1.21em; color: #999ea4">LLM</a> <a href="/tags/%E6%90%9C%E7%B4%A2/" style="font-size: 1.1em; color: #999">搜索</a> <a href="/tags/Logback/" style="font-size: 1.1em; color: #999">Logback</a> <a href="/tags/%E6%95%A3%E5%88%97/" style="font-size: 1.16em; color: #999b9e">散列</a> <a href="/tags/Type-Class/" style="font-size: 1.1em; color: #999">Type Class</a> <a href="/tags/WebSocket/" style="font-size: 1.1em; color: #999">WebSocket</a> <a href="/tags/Go/" style="font-size: 1.33em; color: #99a2af">Go</a> <a href="/tags/Compound-AI-Systems/" style="font-size: 1.1em; color: #999">Compound AI Systems</a> <a href="/tags/%E6%9C%AA%E5%AE%8C%E6%88%90/" style="font-size: 1.1em; color: #999">未完成</a> <a href="/tags/%E7%BF%BB%E8%AF%91/" style="font-size: 1.16em; color: #999b9e">翻译</a> <a href="/tags/Ethereum/" style="font-size: 1.44em; color: #99a7ba">Ethereum</a> <a href="/tags/Java/" style="font-size: 1.5em; color: #99a9bf">Java</a> <a href="/tags/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/" style="font-size: 1.1em; color: #999">数据挖掘</a> <a href="/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/" style="font-size: 1.1em; color: #999">机器学习</a> <a href="/tags/%E7%AE%A1%E7%90%86/" style="font-size: 1.39em; color: #99a4b4">管理</a> <a href="/tags/%E5%A5%87%E6%80%9D%E5%A6%99%E6%83%B3/" style="font-size: 1.16em; color: #999b9e">奇思妙想</a> <a href="/tags/%E6%80%9D%E7%BB%B4%E6%A8%A1%E5%BC%8F/" style="font-size: 1.1em; color: #999">思维模式</a> <a href="/tags/%E6%B5%8B%E8%AF%95/" style="font-size: 1.16em; color: #999b9e">测试</a> <a href="/tags/%E6%8D%89%E8%99%AB/" style="font-size: 1.16em; color: #999b9e">捉虫</a> <a href="/tags/misc/" style="font-size: 1.1em; color: #999">misc</a> <a href="/tags/OpenCode/" style="font-size: 1.27em; color: #99a0a9">OpenCode</a> <a href="/tags/SDD/" style="font-size: 1.21em; color: #999ea4">SDD</a> <a href="/tags/MapReduce/" style="font-size: 1.1em; color: #999">MapReduce</a> <a href="/tags/%E5%8D%95%E5%85%83%E5%8C%96/" style="font-size: 1.1em; color: #999">单元化</a> <a href="/tags/JDK/" style="font-size: 1.1em; color: #999">JDK</a> <a href="/tags/%E5%8D%9A%E5%BC%88%E8%AE%BA/" style="font-size: 1.1em; color: #999">博弈论</a></div></div><div class="card-widget card-archives">
<div class="item-headline">
<i class="fas fa-archive"></i>
<span>Archives</span>
<a class="card-more-btn" href="/archives/"
title="View More">
<i class="fas fa-angle-right"></i>
</a>
</div>
<ul class="card-archive-list">
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2026/04/">
<span class="card-archive-list-date">
April 2026
</span>
<span class="card-archive-list-count">15</span>
</a>
</li>
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2026/03/">
<span class="card-archive-list-date">
March 2026
</span>
<span class="card-archive-list-count">18</span>
</a>
</li>
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2026/02/">
<span class="card-archive-list-date">
February 2026
</span>
<span class="card-archive-list-count">17</span>
</a>
</li>
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2026/01/">
<span class="card-archive-list-date">
January 2026
</span>
<span class="card-archive-list-count">5</span>
</a>
</li>
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2025/09/">
<span class="card-archive-list-date">
September 2025
</span>
<span class="card-archive-list-count">2</span>
</a>
</li>
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2025/08/">
<span class="card-archive-list-date">
August 2025
</span>
<span class="card-archive-list-count">2</span>
</a>
</li>
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2025/07/">
<span class="card-archive-list-date">
July 2025
</span>
<span class="card-archive-list-count">15</span>
</a>
</li>
<li class="card-archive-list-item">
<a class="card-archive-list-link" href="/archives/2025/05/">
<span class="card-archive-list-date">
May 2025
</span>
<span class="card-archive-list-count">1</span>
</a>
</li>
</ul>
</div><div class="card-widget card-webinfo"><div class="item-headline"><i class="fas fa-chart-line"></i><span>Website Info</span></div><div class="webinfo"><div class="webinfo-item"><div class="item-name">Article Count :</div><div class="item-count">377</div></div><div class="webinfo-item"><div class="item-name">Total Word Count :</div><div class="item-count">2041.7k</div></div><div class="webinfo-item"><div class="item-name">Unique Visitors :</div><div class="item-count" id="busuanzi_value_site_uv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Page Views :</div><div class="item-count" id="busuanzi_value_site_pv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">Last Update :</div><div class="item-count" id="last-push-date" data-lastPushDate="2026-04-28T08:57:01.843Z"><i class="fa-solid fa-spinner fa-spin"></i></div></div></div></div></div></div></main><footer id="footer"><div class="footer-other"><div class="footer-copyright"><span class="copyright">© 2017 - 2026 By magicliang</span><span class="framework-info"><span>Framework </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo 8.1.1</a><span class="footer-separator">|</span><span>Theme </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly 5.5.4</a></span></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="translateLink" type="button" title="Toggle Between Traditional and Simplified Chinese">簡</button><button id="darkmode" type="button" title="Toggle Between Light and Dark Mode"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="Toggle Between Single-column and Double-column"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="Settings"><i class="fas fa-cog fa-spin"></i></button><button id="go-up" type="button" title="Back to Top"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js?v=5.5.4"></script><script src="/js/main.js?v=5.5.4"></script><script src="/js/tw_cn.js?v=5.5.4"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar@0.1.16/dist/snackbar.min.js"></script><div class="js-pjax"><script>(() => {
const parseViewBox = viewBox => {
if (!viewBox) return null
const parts = viewBox.trim().split(/[\s,]+/).map(n => Number(n))
if (parts.length !== 4 || parts.some(n => Number.isNaN(n))) return null
return parts
}
const getSvgViewBox = svg => {
const attr = parseViewBox(svg.getAttribute('viewBox'))
if (attr) return attr
// Fallback: use bbox to build a viewBox
try {
const bbox = svg.getBBox()
if (bbox && bbox.width && bbox.height) return [bbox.x, bbox.y, bbox.width, bbox.height]
} catch (e) {
// getBBox may fail on some edge cases; ignore
}
const w = Number(svg.getAttribute('width')) || 0
const h = Number(svg.getAttribute('height')) || 0
if (w > 0 && h > 0) return [0, 0, w, h]
return [0, 0, 100, 100]
}
const setSvgViewBox = (svg, vb) => {
svg.setAttribute('viewBox', `${vb[0]} ${vb[1]} ${vb[2]} ${vb[3]}`)
}
const clamp = (v, min, max) => Math.max(min, Math.min(max, v))
const openSvgInNewTab = ({ source, initViewBox }) => {
const getClonedSvg = () => {
if (typeof source === 'string') {
const template = document.createElement('template')
template.innerHTML = source.trim()
const svg = template.content.querySelector('svg')
return svg ? svg.cloneNode(true) : null
}
if (source && typeof source.cloneNode === 'function') {
return source.cloneNode(true)
}
return null
}
const clone = getClonedSvg()
if (!clone) return
if (initViewBox && initViewBox.length === 4) {
clone.setAttribute('viewBox', initViewBox.join(' '))
}
if (!clone.getAttribute('xmlns')) clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
if (!clone.getAttribute('xmlns:xlink') && clone.outerHTML.includes('xlink:')) {
clone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink')
}
// inject background to match current theme
const isDark = document.documentElement.getAttribute('data-theme') === 'dark'
const bg = getComputedStyle(document.body).backgroundColor || (isDark ? '#1e1e1e' : '#ffffff')
if (!clone.style.background) clone.style.background = bg
const serializer = new XMLSerializer()
const svgSource = serializer.serializeToString(clone)
const htmlSource = `<!doctype html><html><head><meta charset="utf-8" />
<style>
html, body { width: 100%; height: 100%; margin: 0; display: flex; align-items: center; justify-content: center; background: ${bg}; }
svg { max-width: 100%; max-height: 100%; height: auto; width: auto; }
</style>
</head><body>${svgSource}</body></html>`
const blob = new Blob([htmlSource], { type: 'text/html;charset=utf-8' })
const url = URL.createObjectURL(blob)
window.open(url, '_blank', 'noopener')
setTimeout(() => URL.revokeObjectURL(url), 30000)
}
const attachMermaidViewerButton = wrap => {
let btn = wrap.querySelector('.mermaid-open-btn')
if (!btn) {
btn = document.createElement('button')
btn.type = 'button'
btn.className = 'mermaid-open-btn'
wrap.appendChild(btn)
}
btn.innerHTML = '<i class="fa fa-search fa-fw" aria-hidden="true"></i>'
if (!btn.__mermaidViewerBound) {
btn.addEventListener('click', e => {
e.preventDefault()
e.stopPropagation()
const svg = wrap.__mermaidOriginalSvg || wrap.querySelector('svg')
if (!svg) return
const initViewBox = wrap.__mermaidInitViewBox
if (typeof svg === 'string') {
openSvgInNewTab({ source: svg, initViewBox })
return
}
openSvgInNewTab({ source: svg, initViewBox })
})
btn.__mermaidViewerBound = true
}
}
// Zoom around a point (px, py) in the SVG viewport (in viewBox coordinates)
const zoomAtPoint = (vb, factor, px, py) => {
const w = vb[2] * factor
const h = vb[3] * factor
const nx = px - (px - vb[0]) * factor
const ny = py - (py - vb[1]) * factor
return [nx, ny, w, h]
}
const initMermaidGestures = wrap => {
const svg = wrap.querySelector('svg')
if (!svg) return
// Ensure viewBox exists so gestures always work
const initVb = getSvgViewBox(svg)
wrap.__mermaidInitViewBox = initVb
wrap.__mermaidCurViewBox = initVb.slice()
setSvgViewBox(svg, initVb)
// Avoid binding multiple times on themeChange/pjax
if (wrap.__mermaidGestureBound) return
wrap.__mermaidGestureBound = true
// Helper: map client (viewport) coordinate -> viewBox coordinate
const clientToViewBox = (clientX, clientY) => {
const rect = svg.getBoundingClientRect()
const vb = wrap.__mermaidCurViewBox || getSvgViewBox(svg)
const x = vb[0] + (clientX - rect.left) * (vb[2] / rect.width)
const y = vb[1] + (clientY - rect.top) * (vb[3] / rect.height)
return { x, y, rect, vb }
}
const state = {
pointers: new Map(),
startVb: null,
startDist: 0,
startCenter: null
}
const clampVb = vb => {
const init = wrap.__mermaidInitViewBox || vb
const minW = init[2] * 0.1
const maxW = init[2] * 10
const minH = init[3] * 0.1
const maxH = init[3] * 10
vb[2] = clamp(vb[2], minW, maxW)
vb[3] = clamp(vb[3], minH, maxH)
return vb
}
const setCurVb = vb => {
vb = clampVb(vb)
wrap.__mermaidCurViewBox = vb
setSvgViewBox(svg, vb)
}
const onPointerDown = e => {
// Allow only primary button for mouse
if (e.pointerType === 'mouse' && e.button !== 0) return
svg.setPointerCapture(e.pointerId)
state.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
if (state.pointers.size === 1) {
state.startVb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
} else if (state.pointers.size === 2) {
const pts = [...state.pointers.values()]
const dx = pts[0].x - pts[1].x
const dy = pts[0].y - pts[1].y
state.startDist = Math.hypot(dx, dy)
state.startVb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
state.startCenter = { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }
}
}
const onPointerMove = e => {
if (!state.pointers.has(e.pointerId)) return
state.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
// Pan with 1 pointer
if (state.pointers.size === 1 && state.startVb) {
const p = [...state.pointers.values()][0]
const prev = { x: e.clientX - e.movementX, y: e.clientY - e.movementY }
// movementX/Y unreliable on touch, compute from stored last position
const last = wrap.__mermaidLastSinglePointer || p
const dxClient = p.x - last.x
const dyClient = p.y - last.y
wrap.__mermaidLastSinglePointer = p
const { rect } = clientToViewBox(p.x, p.y)
const vb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
const dx = dxClient * (vb[2] / rect.width)
const dy = dyClient * (vb[3] / rect.height)
setCurVb([vb[0] - dx, vb[1] - dy, vb[2], vb[3]])
return
}
// Pinch zoom with 2 pointers
if (state.pointers.size === 2 && state.startVb && state.startDist > 0) {
const pts = [...state.pointers.values()]
const dx = pts[0].x - pts[1].x
const dy = pts[0].y - pts[1].y
const dist = Math.hypot(dx, dy)
if (!dist) return
const factor = state.startDist / dist // dist bigger => zoom in (viewBox smaller)
const cx = (pts[0].x + pts[1].x) / 2
const cy = (pts[0].y + pts[1].y) / 2
const centerClient = { x: cx, y: cy }
const pxy = clientToViewBox(centerClient.x, centerClient.y)
const cpx = pxy.x
const cpy = pxy.y
const vb = zoomAtPoint(state.startVb, factor, cpx, cpy)
setCurVb(vb)
}
}
const onPointerUpOrCancel = e => {
state.pointers.delete(e.pointerId)
if (state.pointers.size === 0) {
state.startVb = null
state.startDist = 0
state.startCenter = null
wrap.__mermaidLastSinglePointer = null
} else if (state.pointers.size === 1) {
// reset single pointer baseline to avoid jump
wrap.__mermaidLastSinglePointer = [...state.pointers.values()][0]
}
}
// Wheel zoom (mouse/trackpad)
const onWheel = e => {
// ctrlKey on mac trackpad pinch; we treat both as zoom
e.preventDefault()
const delta = e.deltaY
const zoomFactor = delta > 0 ? 1.1 : 0.9
const { x, y } = clientToViewBox(e.clientX, e.clientY)
const vb = (wrap.__mermaidCurViewBox || getSvgViewBox(svg)).slice()
setCurVb(zoomAtPoint(vb, zoomFactor, x, y))
}
const onDblClick = () => {
const init = wrap.__mermaidInitViewBox
if (!init) return
wrap.__mermaidCurViewBox = init.slice()
setSvgViewBox(svg, init)
}
svg.addEventListener('pointerdown', onPointerDown)
svg.addEventListener('pointermove', onPointerMove)
svg.addEventListener('pointerup', onPointerUpOrCancel)
svg.addEventListener('pointercancel', onPointerUpOrCancel)
svg.addEventListener('wheel', onWheel, { passive: false })
svg.addEventListener('dblclick', onDblClick)
}
const runMermaid = ele => {
window.loadMermaid = true
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'default'
ele.forEach((item, index) => {
const mermaidSrc = item.firstElementChild
// Clear old render (themeChange/pjax will rerun)
const oldSvg = item.querySelector('svg')
if (oldSvg) oldSvg.remove()
item.__mermaidGestureBound = false
const config = mermaidSrc.dataset.config ? JSON.parse(mermaidSrc.dataset.config) : {}
if (!config.theme) {
config.theme = theme
}
const mermaidThemeConfig = `%%{init: ${JSON.stringify(config)}}%%\n`
const mermaidID = `mermaid-${index}`
const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent
const renderFn = mermaid.render(mermaidID, mermaidDefinition)
const renderMermaid = svg => {
mermaidSrc.insertAdjacentHTML('afterend', svg)
if (true) initMermaidGestures(item)
item.__mermaidOriginalSvg = svg
if (true) attachMermaidViewerButton(item)
}
// mermaid v9 and v10 compatibility
typeof renderFn === 'string' ? renderMermaid(renderFn) : renderFn.then(({ svg }) => renderMermaid(svg))
})
}
const codeToMermaid = () => {
const codeMermaidEle = document.querySelectorAll('pre > code.mermaid')
if (codeMermaidEle.length === 0) return
codeMermaidEle.forEach(ele => {
const preEle = document.createElement('pre')
preEle.className = 'mermaid-src'
preEle.hidden = true
preEle.textContent = ele.textContent
const newEle = document.createElement('div')
newEle.className = 'mermaid-wrap'
newEle.appendChild(preEle)
ele.parentNode.replaceWith(newEle)
})
}
const loadMermaid = () => {
if (true) codeToMermaid()
const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap')
if ($mermaid.length === 0) return
const runMermaidFn = () => runMermaid($mermaid)
btf.addGlobalFn('themeChange', runMermaidFn, 'mermaid')
window.loadMermaid ? runMermaidFn() : btf.getScript('https://cdn.jsdelivr.net/npm/mermaid@11.12.2/dist/mermaid.min.js').then(runMermaidFn)
}
btf.addGlobalFn('encrypt', loadMermaid, 'mermaid')
window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid)
})()</script></div><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1.1.6/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="true"></script><script id="canvas_nest" defer="defer" color="0,0,255" opacity="0.7" zIndex="-1" count="99" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1.1.6/dist/canvas-nest.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1.1.6/dist/activate-power-mode.min.js"></script><script>POWERMODE.colorful = true;
POWERMODE.shake = true;
POWERMODE.mobile = false;
document.body.addEventListener('input', POWERMODE);
</script><script id="click-heart" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1.1.6/dist/click-heart.min.js" async="async" mobile="false"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>