forked from mxx1111/spare-cycles
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
1089 lines (1024 loc) · 85.8 KB
/
Copy pathindex.html
File metadata and controls
1089 lines (1024 loc) · 85.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
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html lang="zh-CN" data-lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spare Cycles · 余力</title>
<meta name="description" content="想让人帮你改代码,又不能把代码给他。sparepack 把私有仓库切成只含接口和测试的任务包,业务逻辑不出门。">
<meta property="og:title" content="Spare Cycles · 余力">
<meta property="og:description" content="Get help with your code without handing over your codebase.">
<meta property="og:type" content="website">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>♺</text></svg>">
<style>
:root {
--bg: #F0EEE6;
--bg-raised: #FAF9F5;
--ink: #1F1E1D;
--ink-soft: #605C54;
--ink-faint: #8A867C;
--accent: #C4633F;
--accent-soft: #D97757;
--rule: #DFDBD0;
--code-bg: #292824;
--code-ink: #E8E4D9;
--ok: #3F6B4E;
--warn: #9A6B2F;
--serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Songti SC", "Source Han Serif SC", serif;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
/* One width for everything: every block shares the same left and right
edge. Dial this number down if lines start feeling too long to track. */
--page: 1240px;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #1B1A18;
--bg-raised: #24231F;
--ink: #EDEAE0;
--ink-soft: #A8A297;
--ink-faint: #7C766B;
--accent: #E08A6B;
--accent-soft: #D97757;
--rule: #35332D;
--code-bg: #121110;
--code-ink: #E8E4D9;
--ok: #7FB08D;
--warn: #D3A353;
}
}
:root[data-theme="dark"] {
--bg: #1B1A18;
--bg-raised: #24231F;
--ink: #EDEAE0;
--ink-soft: #A8A297;
--ink-faint: #7C766B;
--accent: #E08A6B;
--accent-soft: #D97757;
--rule: #35332D;
--code-bg: #121110;
--code-ink: #E8E4D9;
--ok: #7FB08D;
--warn: #D3A353;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
font-size: 17px;
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
.wrap { max-width: var(--page); margin: 0 auto; padding: 0 24px; }
.wide { max-width: var(--page); }
/* --- language switching --- */
[data-en] { display: none; }
html[data-lang="en"] [data-zh] { display: none; }
html[data-lang="en"] [data-en] { display: revert; }
/* --- nav --- */
nav {
position: sticky; top: 0; z-index: 20;
background: color-mix(in srgb, var(--bg) 96%, transparent);
backdrop-filter: saturate(180%) blur(12px);
border-bottom: 1px solid transparent;
transition: border-color .2s;
}
nav.stuck { border-bottom-color: var(--rule); }
nav .wrap { display: flex; align-items: center; gap: 20px; height: 60px; }
.brand { font-family: var(--serif); font-size: 19px; font-weight: 600; letter-spacing: -.01em; text-decoration: none; color: var(--ink); }
.brand span { color: var(--accent); }
nav .spacer { flex: 1; }
nav a.link { color: var(--ink-soft); text-decoration: none; font-size: 14.5px; }
nav a.link:hover { color: var(--ink); }
.toggle {
font: inherit; font-size: 13px; cursor: pointer;
background: transparent; color: var(--ink-soft);
border: 1px solid var(--rule); border-radius: 999px;
padding: 5px 12px; line-height: 1; white-space: nowrap; min-width: 52px;
}
.toggle:hover { color: var(--ink); border-color: var(--ink-faint); }
@media (max-width: 400px) {
.wrap { padding: 0 18px; }
nav .wrap { gap: 12px; }
nav a.link { font-size: 13.5px; }
nav a.link.opt { display: none; }
}
/* --- hero --- */
header { padding: 92px 0 72px; }
h1 {
font-family: var(--serif);
font-size: clamp(34px, 6.2vw, 56px);
line-height: 1.12;
letter-spacing: -.02em;
font-weight: 600;
margin: 0 0 24px;
}
.lede { font-size: clamp(18px, 2.4vw, 22px); color: var(--ink-soft); margin: 0 0 36px; }
.cta { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
.btn {
display: inline-flex; align-items: center; gap: 8px;
background: var(--accent); color: #fff; text-decoration: none;
padding: 11px 20px; border-radius: 8px; font-size: 15px; font-weight: 500;
transition: background .15s, transform .15s;
}
.btn:hover { background: var(--accent-soft); transform: translateY(-1px); }
.btn.ghost { background: transparent; color: var(--ink); border: 1px solid var(--rule); }
.btn.ghost:hover { background: var(--bg-raised); border-color: var(--ink-faint); }
/* --- sections --- */
section { padding: 56px 0; border-top: 1px solid var(--rule); scroll-margin-top: 72px; }
@media (min-width: 1040px) {
/* A longer line needs a larger glyph and a taller line to stay trackable. */
body { font-size: 18.5px; line-height: 1.72; }
.wrap { padding: 0 32px; }
header { padding: 120px 0 88px; }
section { padding: 80px 0; }
/* Code lines stay short so they survive a 390px phone; on a wide column
the way to fill the space is a larger glyph, not a longer line. */
pre { font-size: 15px; padding: 22px 24px; }
}
h2, h3 { scroll-margin-top: 76px; }
h2 {
font-family: var(--serif); font-size: clamp(25px, 3.6vw, 33px);
line-height: 1.22; letter-spacing: -.015em; font-weight: 600; margin: 0 0 20px;
}
h3 { font-size: 18px; font-weight: 600; margin: 32px 0 10px; letter-spacing: -.005em; }
p { margin: 0 0 18px; }
p.tight { margin-bottom: 10px; }
.muted { color: var(--ink-soft); }
.small { font-size: 15px; }
a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
strong { font-weight: 650; }
/* --- callout --- */
.callout {
background: var(--bg-raised); border: 1px solid var(--rule);
border-left: 3px solid var(--accent); border-radius: 6px;
padding: 20px 22px; margin: 24px 0;
}
.callout p:last-child { margin-bottom: 0; }
.callout .label {
font-size: 12px; letter-spacing: .08em; text-transform: uppercase;
color: var(--accent); font-weight: 650; margin-bottom: 8px;
}
blockquote {
margin: 18px 0; padding-left: 18px; border-left: 2px solid var(--rule);
color: var(--ink-soft); font-size: 16px;
}
/* --- code --- */
pre {
background: var(--code-bg); color: var(--code-ink);
padding: 18px 20px; border-radius: 8px; overflow-x: auto;
font-family: var(--mono); font-size: 13.5px; line-height: 1.6;
margin: 20px 0;
}
pre .c { color: #8A8577; }
pre .g { color: #8FBF9F; }
pre .r { color: #E08A6B; }
pre .k { color: #9CBFDB; }
pre .s { color: #D6C48A; }
code:not(pre code) {
font-family: var(--mono); font-size: .88em;
background: var(--bg-raised); border: 1px solid var(--rule);
padding: 1.5px 5px; border-radius: 4px;
}
/* --- before/after --- */
.ba { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 24px 0; }
.ba > div { min-width: 0; }
.ba h4 { margin: 0 0 8px; font-size: 12.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-faint); font-weight: 650; }
.ba pre { margin: 0; height: 100%; }
@media (max-width: 1040px) {
.ba { grid-template-columns: 1fr; }
.ba pre { height: auto; }
.ba > div + div h4 { margin-top: 8px; }
}
/* --- highlight cards --- */
.hl { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: 8px; overflow: hidden; margin: 26px 0; }
.hl > div { background: var(--bg); padding: 20px 22px; }
.hl .n { font-family: var(--mono); font-size: 12px; color: var(--accent); font-weight: 650; }
.hl b { display: block; font-size: 16.5px; margin: 4px 0 8px; letter-spacing: -.005em; }
.hl p { margin: 0; font-size: 15px; color: var(--ink-soft); }
@media (max-width: 640px) { .hl { grid-template-columns: 1fr; } }
/* --- tiers --- */
.tiers { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: 8px; overflow: hidden; margin: 24px 0; }
.tier { background: var(--bg); padding: 18px 20px; display: grid; grid-template-columns: 62px 1fr; gap: 16px; align-items: start; }
.tier .t { font-family: var(--mono); font-size: 13px; font-weight: 650; color: var(--accent); padding-top: 2px; }
.tier .d { font-size: 15.5px; }
.tier .d b { display: block; margin-bottom: 3px; }
.tier .d span { color: var(--ink-soft); }
@media (max-width: 560px) { .tier { grid-template-columns: 1fr; gap: 4px; } }
/* --- tables --- */
.tw { overflow-x: auto; margin: 22px 0; }
table { width: 100%; border-collapse: collapse; font-size: 15px; }
th, td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--rule); vertical-align: top; }
th { font-size: 12px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-faint); font-weight: 650; white-space: nowrap; }
tr:last-child td { border-bottom: none; }
td code { white-space: nowrap; }
table.num td:last-child, table.num th:last-child { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
td .why { display: block; color: var(--ink-soft); font-size: 14.5px; margin-top: 2px; }
/* --- steps --- */
ol.steps { counter-reset: s; list-style: none; padding: 0; margin: 24px 0; }
ol.steps li { counter-increment: s; position: relative; padding-left: 42px; margin-bottom: 18px; }
ol.steps li::before {
content: counter(s); position: absolute; left: 0; top: -1px;
width: 26px; height: 26px; border-radius: 50%;
background: var(--bg-raised); border: 1px solid var(--rule);
display: grid; place-items: center;
font-size: 13px; font-weight: 650; color: var(--accent);
}
/* --- status --- */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: 8px; overflow: hidden; margin: 24px 0; }
.stat { background: var(--bg); padding: 18px 20px; }
.stat .n { font-family: var(--serif); font-size: 27px; font-weight: 600; letter-spacing: -.01em; display: block; }
.stat .k { font-size: 13px; color: var(--ink-soft); margin-top: 2px; }
/* --- faq --- */
.faq { border-top: 1px solid var(--rule); margin: 26px 0 0; }
details { border-bottom: 1px solid var(--rule); }
details summary {
cursor: pointer; padding: 15px 0; font-weight: 600; font-size: 16.5px;
list-style: none; display: flex; gap: 12px; align-items: baseline;
}
details summary::-webkit-details-marker { display: none; }
details summary::before { content: "+"; color: var(--accent); font-family: var(--mono); font-weight: 400; flex: none; }
details[open] summary::before { content: "\2013"; }
details summary:hover { color: var(--accent); }
.a { padding: 0 0 18px 24px; color: var(--ink-soft); font-size: 15.5px; }
.a p { margin: 0 0 10px; }
.a p:last-child { margin: 0; }
.a pre { margin: 12px 0 0; }
ul.plain { padding-left: 20px; margin: 16px 0; }
ul.plain li { margin-bottom: 8px; }
footer { border-top: 1px solid var(--rule); padding: 40px 0 60px; color: var(--ink-faint); font-size: 14.5px; }
footer a { color: var(--ink-soft); }
.fl { display: flex; flex-wrap: wrap; gap: 18px; margin-bottom: 14px; }
/* --- timeline --- */
.tl { list-style: none; margin: 28px 0 4px; padding: 0 0 0 20px; border-left: 2px solid var(--rule); }
.tl > li { position: relative; padding: 0 0 22px 24px; }
.tl > li:last-child { padding-bottom: 4px; }
.tl > li::before {
content: ""; position: absolute; left: -7px; top: 7px;
width: 12px; height: 12px; border-radius: 50%;
background: var(--bg); border: 2px solid var(--ink-faint);
}
.tl > li.ok::before { border-color: var(--ok); }
.tl > li.bad::before { border-color: var(--accent); }
.tl time { font-family: var(--mono); font-size: 12.5px; color: var(--ink-faint); letter-spacing: .02em; }
.tl .who { font-weight: 650; }
.tl p { margin: 5px 0 0; font-size: 15.5px; color: var(--ink-soft); }
.tl p b { color: var(--ink); }
/* --- leaderboard --- */
.lb { margin: 8px 0 4px; }
.lb .tw { margin: 0 0 14px; }
.lb .pos { font-family: var(--mono); font-size: 13px; color: var(--ink-faint); }
.lb .pos.first { color: var(--accent); font-weight: 650; }
.lb td .tag { display: inline-block; font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase; font-weight: 650; padding: 1px 7px; border-radius: 4px; margin-left: 6px; vertical-align: 1px; }
.lb .tag.delivered { color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 45%, transparent); }
.lb .tag.wasted { color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent); }
.lb .tag.words { color: var(--ink-faint); border: 1px solid var(--rule); }
.lb .note { font-size: 14.5px; color: var(--ink-soft); margin: 0; }
/* TP sits second from last here, not last: right-align the number column and
let the standing column read as ordinary left-aligned prose. */
.lb table.num td:last-child, .lb table.num th:last-child { text-align: left; white-space: normal; }
.lb table.num td:nth-last-child(2), .lb table.num th:nth-last-child(2) {
text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums;
}
</style>
</head>
<body>
<nav id="nav">
<div class="wrap wide">
<a class="brand" href="#top">Spare<span>Cycles</span></a>
<div class="spacer"></div>
<a class="link opt" href="https://github.com/mxx1111/sparepack">sparepack</a>
<a class="link" href="https://github.com/mxx1111/spare-cycles">GitHub</a>
<button class="toggle" id="lang" type="button">EN</button>
</div>
</nav>
<div class="wrap" id="top">
<header>
<h1>
<span data-zh>想让人帮你改代码,<br>又不能把代码给他</span>
<span data-en>Get help with your code<br>without handing over your codebase</span>
</h1>
<p class="lede">
<span data-zh><code>sparepack</code> 把私有仓库切成只含接口、验收测试和假数据的任务包。别人对着测试写实现,你的业务逻辑一行都没离开过本机。</span>
<span data-en><code>sparepack</code> slices a private repository down to interfaces, acceptance tests, and synthetic fixtures. Someone implements against the tests; your business logic never leaves your machine.</span>
</p>
<div class="cta">
<a class="btn" href="https://www.npmjs.com/package/sparepack">
<span data-zh>安装 sparepack</span><span data-en>Install sparepack</span>
</a>
<a class="btn ghost" href="https://github.com/mxx1111/spare-cycles/issues">
<span data-zh>看任务板</span><span data-en>See the task board</span>
</a>
</div>
</header>
<section>
<h2><span data-zh>先说这不是什么</span><span data-en>What this is not</span></h2>
<p>
<span data-zh><strong>这不是共享、出借、合池或转售 AI 订阅额度。</strong>那违反 Anthropic 条款,做了就是等封号。</span>
<span data-en><strong>This is not a way to share, lend, pool, or resell AI subscription quota.</strong> That is against Anthropic's terms and gets accounts banned.</span>
</p>
<blockquote>
You may not share your Account login information, Anthropic API key, or Account credentials with anyone else. You also may not make your Account available to anyone else.
</blockquote>
<p>
<span data-zh>2026 年 2 月 Anthropic 进一步禁止把 Pro/Max 的 OAuth token 用在 Claude Code 和 claude.ai 之外,也禁止代表他人把请求路由过你的 seat——即使量很小,即使只是团队内部工具。</span>
<span data-en>In February 2026 Anthropic went further, prohibiting Pro/Max OAuth tokens anywhere outside Claude Code and claude.ai, including routing other people's requests through your seat — even at low volume, even for an internal team tool.</span>
</p>
<p>
<span data-zh>所以这里没有额度池、没有 token 中转、没有代理、没有共享账号。有余力的人<strong>自己把活干了</strong>,交出去的是成果(一个 PR),不是账号访问权。那就是普通的外包,条款上是干净的。</span>
<span data-en>So there are no quota pools, no token relays, no proxies, no shared accounts. People with spare capacity <strong>do the work themselves</strong> and hand over the result — a pull request — not access to their account. That is ordinary freelance work, and it is fine.</span>
</p>
<p class="muted small">
<span data-zh>真正稀缺的东西从来不是 token,是有时间又愿意干活的人。</span>
<span data-en>The scarce resource was never tokens. It was people with time and willingness.</span>
</p>
</section>
<section id="why">
<h2><span data-zh>四件可以自己动手核实的事</span><span data-en>Four things you can check yourself</span></h2>
<p>
<span data-zh>下面每一条都不是形容词,是一条能跑的命令或者一行能读的代码。这也是唯一值得写在落地页上的那种卖点。</span>
<span data-en>None of the following is an adjective. Each one is a command you can run or a line you can read — the only kind of claim worth putting on a landing page.</span>
</p>
<div class="hl">
<div>
<div class="n">01</div>
<b><span data-zh>一个真的工具,不是一句「注意隐私」</span><span data-en>An actual tool, not a reminder to be careful</span></b>
<p>
<span data-zh>大多数隐私建议止步于建议。sparepack 是 <code>npm install</code> 装得到的命令行工具:白名单制、解析后重建、写盘前强制人工过目。80 个测试,其中一个端到端跑一个塞满凭证和客户记录的仓库。</span>
<span data-en>Most privacy advice stops at advice. sparepack is a CLI you can <code>npm install</code>: allowlist-only, parse-and-rebuild, mandatory human review before anything is written. 80 tests, one of them an end-to-end run against a repo seeded with credentials and customer records.</span>
</p>
</div>
<div>
<div class="n">02</div>
<b><span data-zh>红线写成数据结构,不是写成承诺</span><span data-en>Red lines as data structures, not promises</span></b>
<p>
<span data-zh>「积分不可转让」在这里不是一句保证。账本 schema 里根本不存在 user→user 的转账类型,校验器碰到就当篡改,直接非零退出。想违规都没有地方放。</span>
<span data-en>"Points are not transferable" is not a pledge here. The ledger schema has no user-to-user transfer type at all; the verifier treats one as tampering and exits non-zero. There is nowhere to put the violation.</span>
</p>
</div>
<div>
<div class="n">03</div>
<b><span data-zh>账本谁都能从零重算</span><span data-en>Anyone can recompute the ledger</span></b>
<p>
<span data-zh>17 条记录、9 条不变量、一条命令。余额不是维护者说了算,是从完整历史重放出来的。快照和重算对不上,以重算为准。</span>
<span data-en>17 entries, 9 invariants, one command. Balances are not asserted by the maintainer, they are replayed from the full history. If the snapshot disagrees with the replay, the replay wins.</span>
</p>
</div>
<div>
<div class="n">04</div>
<b><span data-zh>不灵的地方也写在明面上</span><span data-en>The gaps are written down too</span></b>
<p>
<span data-zh>合规执行表里有两行明确标着「未强制」。账本文件头记着一次时间戳更正的完整经过。被折叠的 19 条垃圾评论逐条列在治理日志里。这不是为了显得诚实,是为了不让人读完文档以为保护比实际更多。</span>
<span data-en>Two rows of the enforcement table say "not enforced" in as many words. The ledger header documents a one-time timestamp correction in full. All 19 minimized spam comments are itemised in the governance log. Not for the look of candour — so nobody finishes the docs assuming more protection than exists.</span>
</p>
</div>
</div>
</section>
<section id="sparepack">
<h2><span data-zh>sparepack 做的事</span><span data-en>What sparepack does</span></h2>
<p>
<span data-zh>你有个真实的 bug,任务不大,也有人愿意修。但那个文件里全是你的定价规则、客户数据,还有三年前谁留下的一个硬编码密钥。于是你不问了,bug 就一直在那。</span>
<span data-en>You have a real bug. The task is small and there are people who would happily fix it. But the file is full of your pricing rules, your customers, and a hardcoded key someone left there three years ago. So you do not ask, and the bug stays.</span>
</p>
<div class="ba">
<div>
<h4><span data-zh>你的文件</span><span data-en>Your file</span></h4>
<pre><span class="c">// src/pricing.ts</span>
export function quote(
seats: number, tier: string
): Quote {
<span class="r">const base = tier === "enterprise"</span>
<span class="r">? 4900 : 1900</span>
<span class="r">const discount = seats > 50 ? 0.7</span>
<span class="r">: seats > 10 ? 0.85 : 1</span>
<span class="r">return { cents: Math.round(</span>
<span class="r">base * seats * discount), tier }</span>
}</pre>
</div>
<div>
<h4><span data-zh>发出去的</span><span data-en>What ships</span></h4>
<pre><span class="c">// Stripped by sparepack:</span>
<span class="c">// signatures only.</span>
export function quote(
seats: number, tier: string
): Quote {
<span class="g">throw new Error(</span>
<span class="g">'sparepack stub: not implemented'</span>
<span class="g">)</span>
}</pre>
</div>
</div>
<p style="margin-top: 36px;">
<span data-zh>签名是契约,阈值是商业决策。只有前者需要离开这栋楼。</span>
<span data-en>The signature is a contract. The thresholds are a business decision. Only one of those needs to leave the building.</span>
</p>
<pre><span class="c"># <span data-zh>三个命令</span><span data-en>Three commands</span></span>
npx sparepack init <span class="c"># <span data-zh>生成带注释的配置</span><span data-en>write a commented config</span></span>
npx sparepack pack <span class="c"># <span data-zh>构建、扫描、给你过目、再落盘</span><span data-en>build, scan, show you, then write</span></span>
npx sparepack verify <span class="c"># <span data-zh>从磁盘重新推导,独立复检</span><span data-en>re-derive from disk, check again</span></span></pre>
<h3><span data-zh>配置长这样</span><span data-en>The config</span></h3>
<p class="tight">
<span data-zh>一份 <code>sparepack.yaml</code>,五个键,每个键决定一类文件怎么处理。</span>
<span data-en>One <code>sparepack.yaml</code>, five keys, each deciding how a class of file is handled.</span>
</p>
<pre><span class="k">task</span>: <span class="s">"Stream large CSV imports instead of loading the whole file"</span>
<span class="k">include</span>: <span class="c"># <span data-zh>逐字节原样发布</span><span data-en>published byte for byte</span></span>
- src/importer/types.ts
<span class="k">interfaces</span>: <span class="c"># <span data-zh>留签名,函数体换成会抛错的桩</span><span data-en>signatures kept, bodies stubbed</span></span>
- src/importer/parser.ts
<span class="k">tests</span>: <span class="c"># <span data-zh>这就是任务规约本身</span><span data-en>this is the specification</span></span>
- tests/importer/*.spec.ts
<span class="k">fixtures</span>: <span class="c"># <span data-zh>真结构,假数据</span><span data-en>real structure, synthetic values</span></span>
data/orders.json: <span class="s">shape:5</span>
<span class="k">redact</span>: <span class="c"># <span data-zh>扫描器不可能知道的自家名字</span><span data-en>names the scanner cannot know</span></span>
- pattern: <span class="s">"acme-corp|ACME"</span>
replace: <span class="s">"example-org"</span></pre>
<p class="small muted">
<span data-zh><code>shape:n</code> 读真实 JSON,按同样的键和嵌套重建一份假值,数组截到 n 条。<code>rows:n</code> 保留分隔文件的表头再造 n 行。生成是确定性的——同一份配置打两次包字节一致,所以两个包之间的 diff 一定意味着有东西真的变了。</span>
<span data-en><code>shape:n</code> reads the real JSON and rebuilds it with the same keys and nesting but fake values, capping arrays at n. <code>rows:n</code> keeps a delimited file's header and generates n fake rows. Generation is deterministic — packing twice gives byte-identical output, so a diff between two packs always means something real changed.</span>
</p>
<h3><span data-zh>三条设计原则</span><span data-en>Three design rules</span></h3>
<p class="tight">
<span data-zh><strong>白名单制。</strong>没有 <code>exclude</code> 键,以后也不会有。「除了这些全发出去」意味着一个没人想过的文件会被发布,泄露就是这么发生的。而且匹配不到任何文件的 pattern 直接报错——打错一个字不该悄悄少发东西。</span>
<span data-en><strong>Allowlist only.</strong> There is no <code>exclude</code> key and there will not be one. "Publish everything except…" means a file nobody thought about gets published, which is how leaks happen. A pattern matching nothing is an error rather than a silent no-op, because a typo should not quietly ship less than you meant.</span>
</p>
<p class="tight">
<span data-zh><strong>失败方向朝「少给」。</strong>接口剥离是解析后只输出能被明确识别为契约的东西,不是复制原文再删函数体。前者解析漏了等于 stub 缺东西、你会发现;后者漏了等于源码泄露。不支持的语言直接拒绝,绝不把一个 <code>.py</code> 原样放过还暗示它被剥离过了。</span>
<span data-en><strong>Failures point at giving less.</strong> Stripping parses your file and emits only what it positively recognised as contract. It does not copy the file and delete the bodies — under that design a parser gap publishes your source. Unsupported languages are refused outright, never passed through with the implication that they were stripped.</span>
</p>
<p>
<span data-zh><strong>写盘在确认之后。</strong>整个包在内存里构建、扫描、打印清单,然后问你一句:这份文件清单你敢不敢公开贴出来。答案不是 <code>publish</code> 就一个字节都不写。</span>
<span data-en><strong>Nothing is written before you have seen it.</strong> The pack is built in memory, scanned, and printed as a manifest. Then it asks: would you be comfortable posting this file list in public? Answer anything other than <code>publish</code> and no file is created.</span>
</p>
<h3><span data-zh>扫描器查什么</span><span data-en>What the scanner checks</span></h3>
<p class="tight">
<span data-zh>各家厂商的 API key、私钥块、JWT、带真实密码的连接串、硬编码的密钥赋值、身份证与手机号、内网 IP 段、内部主机名。扫描发生在你的 redact 规则<strong>生效之后</strong>——扫之前只会报出你已经处理掉的东西,扫之后才能验证脱敏是否够用。</span>
<span data-en>API keys from the major providers, private key blocks, JWTs, connection strings carrying real passwords, hardcoded secret assignments, national ID and mobile numbers, private IP ranges, internal hostnames. Scanning happens <strong>after</strong> your redactions are applied — scanning before would report findings you had already handled, and only scanning after can tell you the redactions were enough.</span>
</p>
<p>
<span data-zh>两件它<em>不</em>做的事:命中的内容永远不会被完整打印,报告只给掩码片段和长度——一份会泄露它检出的密钥的报告,比没有报告更糟。以及它努力不喊狼来了:<code>example.com</code>、<code>127.0.0.1</code>、<code>${DB_PASSWORD}</code>、<code><your-token-here></code> 是占位符,不算命中。一个被人学会忽略的检查已经不是检查了。</span>
<span data-en>Two things it will <em>not</em> do. It never prints a finding in full — a report that leaks the secret it detected is worse than no report, so findings carry a masked excerpt and a length. And it works hard not to cry wolf: <code>example.com</code>, <code>127.0.0.1</code>, <code>${DB_PASSWORD}</code>, and <code><your-token-here></code> are placeholders, not findings. A check people learn to ignore has stopped being a check.</span>
</p>
<h3><span data-zh>边界,先说清楚</span><span data-en>Limits, stated up front</span></h3>
<ul class="plain small">
<li><span data-zh>接口剥离只支持 TypeScript 和 JavaScript。别的语言拒绝处理,不做「半处理」。</span><span data-en>Interface stripping is TypeScript and JavaScript only. Other languages are refused rather than half-handled.</span></li>
<li><span data-zh><code>include</code> 里的东西按字节原样发布——那是你的判断不是工具的判断,扫描器是那些文件唯一的兜底。</span><span data-en><code>include</code> publishes verbatim, so anything listed there is your judgement, not sparepack's. The scanner is the only safety net on those files.</span></li>
<li><span data-zh>产出是一组文件,不是一个能跑的工程。没有 <code>package.json</code>、没有 tsconfig,接单者拿到手得先补几行才能 <code>npm test</code>。</span><span data-en>A pack is a set of files, not a runnable project. No <code>package.json</code>, no tsconfig — a worker has to add a few lines before <code>npm test</code> works.</span></li>
<li><span data-zh>包里的路径镜像你仓库里的路径,不能重映射。从 monorepo 根目录打包会得到 <code>packages/api/src/...</code>,通常不是你要的。</span><span data-en>Paths in a pack mirror paths in your repo, with no remapping. Packing from a monorepo root gives you <code>packages/api/src/...</code>, which is usually not what you want.</span></li>
<li><span data-zh>清单不随包走。你终端上看到的清单会列出被丢弃的内部函数名、以及哪些 redact 规则命中了——这两样恰好精确描述了这个包存在的目的是隐藏什么,所以写进包里的 <code>MANIFEST.json</code> 只保留 <code>verify</code> 需要的路径、类型和大小。</span><span data-en>The manifest does not travel. What you see in your terminal lists dropped internal functions by name and which redact patterns fired — both describe precisely what the pack exists to withhold — so the <code>MANIFEST.json</code> inside the pack keeps only what <code>verify</code> needs: paths, kinds, sizes.</span></li>
</ul>
<div class="callout">
<div class="label"><span data-zh>诚实说一句</span><span data-en>Worth saying plainly</span></div>
<p class="small">
<span data-zh>扫描器是词法级的,它找的是模式不是含义。写在注释里的业务规则、看着像普通单词的客户名、你忘了加进脱敏列表的内部代号——这些抓不到。真正决定什么东西被公开的,是那一步人工复核;工具里其余所有东西,存在的意义只是让那次复核成为可能。</span>
<span data-en>The scanner is lexical: it finds patterns, not meaning. A business rule written in a comment, a customer name that looks like an ordinary word, an internal codename you forgot to redact — those get through. The manifest review is what actually decides what gets published; everything else exists to make that review possible.</span>
</p>
</div>
</section>
<section id="tiers">
<h2><span data-zh>隐私分级</span><span data-en>Privacy tiers</span></h2>
<p><span data-zh>每个任务必须声明一级,按接单者能看到多少排序。</span><span data-en>Every task declares exactly one, ordered by how much the worker sees.</span></p>
<div class="tiers">
<div class="tier"><div class="t">P0</div><div class="d">
<b><span data-zh>公开</span><span data-en>Public</span></b>
<span data-zh>代码本来就是公开的,直接给链接。</span><span data-en>The code is already public. Point at it and go.</span></div></div>
<div class="tier"><div class="t">P1</div><div class="d">
<b><span data-zh>脱敏任务包 · 私有项目的默认选择</span><span data-en>Redacted pack · the default for private work</span></b>
<span data-zh>接口、验收测试、假数据。业务逻辑不出门。</span><span data-en>Interfaces, acceptance tests, synthetic fixtures. No business logic.</span></div></div>
<div class="tier"><div class="t">P2</div><div class="d">
<b><span data-zh>一次性沙箱</span><span data-en>Ephemeral sandbox</span></b>
<span data-zh>接单者在自己账号下开 Codespace,代码不落他的磁盘。双方都不是对方的宿主——这是关键。</span><span data-en>The worker opens a Codespace on their own account; code never touches their disk. Neither party is the other's host — that is the point.</span></div></div>
<div class="tier"><div class="t">P3</div><div class="d">
<b><span data-zh>信任圈</span><span data-en>Trust circle</span></b>
<span data-zh>NDA 下的完整访问,只给已有关系的人。</span><span data-en>Full access under NDA, for people you already know.</span></div></div>
</div>
<p class="small muted">
<span data-zh>不知道选哪级:任务能用「一组接口 + 一组测试」描述清楚的,选 P1,这也是绝大多数情况。需要接单者在真实项目里跑起来、点一点才能干的,选 P2。P0 和 P3 是两头的特例。目前只有 P0 和 P1 有实际跑过的记录,P2 有流程文档但还没有真实案例,P3 尚未实现。</span>
<span data-en>Not sure which: if the task can be described by a set of interfaces plus a set of tests, use P1 — that covers most of them. If the worker needs the real project running to do the work at all, use P2. P0 and P3 are the two extremes. Only P0 and P1 have actually been run so far; P2 has a documented process but no real case yet, and P3 is not implemented.</span>
</p>
</section>
<section id="board">
<h2><span data-zh>任务板怎么跑</span><span data-en>How the board works</span></h2>
<p><span data-zh>没有网站,没有服务器,全在 GitHub 上。</span><span data-en>No website, no server. The whole thing runs on GitHub.</span></p>
<ol class="steps">
<li><span data-zh><b>发单</b>——开一个 issue,你的积分进托管。余额不够就发不出去。</span><span data-en><b>Post</b> — open an issue. Your points go into escrow. Insufficient balance, no task.</span></li>
<li><span data-zh><b>接单</b>——评论 <code>/claim</code>,等 issue 被指派给你<em>之后</em>再动手。同时 2 个、每周 5 个。真人手工干活本来就是这个量级,机器刷单会立刻撞墙。</span><span data-en><b>Claim</b> — comment <code>/claim</code>, and start only <em>after</em> the issue is assigned to you. Two concurrent, five per week: that is the throughput of a person working by hand, and a bot hits the wall immediately.</span></li>
<li><span data-zh><b>交付</b>——往<em>你的</em>仓库提 PR,附上「这些改动我亲自审阅过、没共享任何凭证」的声明。</span><span data-en><b>Deliver</b> — a PR against <em>your</em> repo, with an attestation that they reviewed it themselves and shared no credentials.</span></li>
<li><span data-zh><b>结算</b>——你合并,账本记一笔,双方档案更新。</span><span data-en><b>Settle</b> — you merge, the ledger records it, both profiles update.</span></li>
</ol>
<p class="small muted">
<span data-zh>这个仓库永远不托管你的代码。任务元数据、账本和信誉在这里,代码本身在双方自己的仓库之间直接流转。</span>
<span data-en>This repository never hosts your code. Task metadata, the ledger, and reputation live here; the code moves directly between the two parties' own repositories.</span>
</p>
<h3><span data-zh>定价:只看复杂度</span><span data-en>Pricing: complexity only</span></h3>
<p class="tight">
<span data-zh>耗额度的任务分三档,<strong>没有 XL</strong>——再大的必须拆。大额单任务是纠纷的来源,而一个做了一半的 XL 对双方都一文不值。</span>
<span data-en>Quota-consuming tasks come in three sizes and <strong>there is no XL</strong> — anything bigger gets split. Large single tasks are where disputes come from, and a half-finished XL is worthless to both sides.</span>
</p>
<div class="tw">
<table class="num">
<tr><th><span data-zh>档位</span><span data-en>Tier</span></th><th><span data-zh>范围</span><span data-en>Scope</span></th><th>TP</th></tr>
<tr><td>S</td><td><span data-zh>≤30 分钟。修 bug、补文档、补一个缺的测试。</span><span data-en>≤30 min. Bug fix, doc, missing test.</span></td><td>10</td></tr>
<tr><td>M</td><td><span data-zh>30 分钟–2 小时。一个函数、一个组件或一个接口。</span><span data-en>30 min–2 h. One function, component, or endpoint.</span></td><td>30</td></tr>
<tr><td>L</td><td><span data-zh>2 小时–半天。一个完整特性带测试。</span><span data-en>2 h–half a day. A complete feature with tests.</span></td><td>80</td></tr>
</table>
</div>
<p class="tight">
<span data-zh>还有一类<strong>不烧额度</strong>的任务,专门为了让「这周额度用完了」不等于「没法参与」:</span>
<span data-en>A second class of task <strong>burns no quota at all</strong>, so that being out of quota does not mean being locked out:</span>
</p>
<div class="tw">
<table class="num">
<tr><th><span data-zh>类型</span><span data-en>Type</span></th><th><span data-zh>做什么</span><span data-en>Scope</span></th><th>TP</th></tr>
<tr><td><code>review</code></td><td><span data-zh>审别人的交付 PR</span><span data-en>Review someone's delivery PR</span></td><td>5</td></tr>
<tr><td><code>redact-audit</code></td><td><span data-zh>在一个 sparepack 产出公开之前当第二双眼睛</span><span data-en>Second pair of eyes on a sparepack output before it goes public</span></td><td>10</td></tr>
<tr><td><code>spec</code></td><td><span data-zh>替别人写那组定义任务的验收测试</span><span data-en>Write the acceptance tests that define someone's task</span></td><td>15</td></tr>
<tr><td><code>arbitrate</code></td><td><span data-zh>担任争议仲裁(需 5 次以上交付)</span><span data-en>Serve as arbitrator on a dispute (5+ deliveries required)</span></td><td>10</td></tr>
</table>
</div>
<h3><span data-zh>积分是闸门,不是胡萝卜</span><span data-en>Points are a gate, not a carrot</span></h3>
<p>
<span data-zh>积分(TP)只能通过完成任务获得、通过发布任务消耗。<strong>不可转让、不可交易、不能提现</strong>,不过期,没有二级市场,以后也不会有。它的职责是防止有人只发不接,仅此而已。新人 50 TP,刚好够发一个 M 任务。</span>
<span data-en>Task Points are earned by delivering and spent by requesting. <strong>Not transferable, not tradable, no cash value</strong>, no expiry, no secondary market — and there will not be one. Their job is to stop people posting forever without ever delivering. That is all. New members start with 50 TP, enough for one M task.</span>
</p>
<p>
<span data-zh>真正的回报是<strong>一份可验证的公开交付记录</strong>:每次交付都是一个合并进别人真实仓库的 PR。这在找工作或谈外包时是有分量的,刷题记录换不来。</span>
<span data-en>The actual reward is <strong>a verifiable public track record</strong>: every delivery is a merged PR in someone's real repository. That is worth something when you are job hunting, in a way a LeetCode streak is not.</span>
</p>
<h3><span data-zh>出了争议怎么办</span><span data-en>When it goes wrong</span></h3>
<p class="small">
<span data-zh>任一方打 <code>dispute</code> 标签,由一位和本任务无关、有 5 次以上交付的仲裁者接手,目标 3 天出结论:<strong>判交付</strong>(积分给接单者,事后加需求的发布者不退款)、<strong>判退款</strong>、或者<strong>判拆分</strong>并写明理由。每一次裁决都带着仲裁者的 handle 和理由链接写进账本,公开,可上诉一次给另一位仲裁者。</span>
<span data-en>Either party applies the <code>dispute</code> label. An arbitrator with 5+ deliveries and no involvement in the task picks it up, targeting three days: <strong>deliver</strong> (points to the worker; a requester who moves the goalposts afterwards gets no refund), <strong>refund</strong>, or <strong>split</strong> with the reasoning recorded. Every ruling is written into the ledger with the arbitrator's handle and a link to their reasoning. Public, and appealable once to a different arbitrator.</span>
</p>
<p class="small muted">
<span data-zh><strong>验收标准就是合同。</strong>接单时 issue 上没写的东西,不构成拒收的理由。这条对双方都成立,也是为什么任务模板把验收标准设成必填。</span>
<span data-en><strong>Acceptance criteria are the contract.</strong> If it was not in the issue when the task was claimed, it is not grounds for refusing delivery. This cuts both ways, and it is why the task template makes acceptance criteria mandatory.</span>
</p>
</section>
<section id="ledger">
<h2><span data-zh>账本:不用信我,自己算</span><span data-en>The ledger: don't trust me, recompute it</span></h2>
<p>
<span data-zh>任何积分系统最后都会碰到同一个问题:凭什么信运营方报的余额。这里的答案是把余额变成一个<strong>可复现的计算结果</strong>而不是一句声明。</span>
<span data-en>Every points system eventually runs into the same question: why believe the operator's numbers. The answer here is to make balances <strong>a reproducible computation</strong> rather than an assertion.</span>
</p>
<pre><span class="c">$ git clone https://github.com/mxx1111/spare-cycles && cd spare-cycles</span>
$ node ledger/verify.mjs
ledger.jsonl — 17 entries
Balances:
ghzhost 40 TP
manav8498 10 TP
mxx1111 0 TP
in balances 50 TP
in escrow 50 TP
total issued 100 TP
<span class="g">All invariants hold.</span></pre>
<p>
<span data-zh><code>ledger.jsonl</code> 只追加:不改行、不删行、不调顺序,更正一律是新增的冲正条目。<code>balances.json</code> 是派生产物,两者对不上时以重算为准。校验器检查九条不变量,其中余额非负和托管非负是<strong>逐条增量检查</strong>的——中途变成负数、后面又补回来的历史一样判无效。</span>
<span data-en><code>ledger.jsonl</code> is append-only: never edit a line, never delete one, never reorder. Corrections are new compensating entries. <code>balances.json</code> is derived output, and if the two disagree the recomputation wins. The verifier checks nine invariants, and the two non-negativity ones are checked <strong>incrementally</strong> — a history that dips negative in the middle and recovers is still invalid.</span>
</p>
<h3><span data-zh>红线 5 长什么样</span><span data-en>What red line 5 looks like</span></h3>
<p class="tight">
<span data-zh>「积分不可转让」这句承诺,在代码里就是这六行——外加一个「不在这六个里面就是篡改」的判断:</span>
<span data-en>The promise that points cannot be transferred is these six lines, plus a rule that anything outside them is tampering:</span>
</p>
<pre><span class="c">// ledger/verify.mjs</span>
const TYPES = {
<span class="k">grant</span>, <span class="k">escrow</span>, <span class="k">settle</span>, <span class="k">refund</span>, <span class="k">split</span>, <span class="k">adjust</span>
}
<span class="c">// <span data-zh>没有 transfer。用户之间的转账在这个 schema 里不存在,</span><span data-en>No transfer. User-to-user movement does not exist in</span></span>
<span class="c">// <span data-zh>不是「不允许」,是根本无法表达。</span><span data-en>this schema — not disallowed, inexpressible.</span></span>
<span class="r">fail(where, `unknown transaction type "transfer"`
+ ` — COMPLIANCE.md red line 5`)</span></pre>
<div class="callout">
<div class="label"><span data-zh>这条不变量是被撞出来的</span><span data-en>One invariant exists because it broke</span></div>
<p class="small">
<span data-zh>时间戳必须是<strong>观测到的</strong>事件时间——issue 的 <code>createdAt</code>、PR 的 <code>mergedAt</code>——而不是敲一个看起来合理的值。这条写成规则是因为它被违反过:最早的八条记录填的是 12:00、12:01……14:00 这种编造值,单调递增检查全过(编造的递增数字确实在递增),直到一个真实时间戳排在编造值后面,结算直接死锁。现在校验器会拒绝任何未来时间——那是「这个值是敲出来的」唯一能被机器识别的证据。整个更正过程写在账本文件头里。</span>
<span data-en>A timestamp must be an <strong>observed</strong> event time — the issue's <code>createdAt</code>, the PR's <code>mergedAt</code> — not a plausible-looking value someone typed. This is a rule because it was broken: the first eight entries were filled in with tidy invented times, 12:00, 12:01, … 14:00. Monotonicity passed, because invented increasing numbers do increase. It only surfaced when a real timestamp arrived behind an invented one and settlement deadlocked. The verifier now rejects any future-dated entry — the one machine-detectable proof that a time was typed rather than observed. The correction is documented in the ledger's header.</span>
</p>
</div>
<p class="small muted">
<span data-zh>一条局限值得直说:这套检查抓得到过期的快照和被编辑的历史,但抓不到在同一个提交里既改历史又重新生成快照的人。能抓住那种情况的是「只追加」这条规则和一双读 diff 的眼睛。CI 收窄了缺口,没有把它堵死。</span>
<span data-en>One limit worth stating plainly: this catches a stale snapshot and an edited history, but not someone who edits the history <em>and</em> regenerates the snapshot in the same commit. What catches that is the append-only rule and a human reading the diff. CI narrows the gap; it does not close it.</span>
</p>
</section>
<section id="alternatives">
<h2><span data-zh>为什么不用现成的东西</span><span data-en>Why not just use what exists</span></h2>
<p>
<span data-zh>这四类东西都存在,也都比这个项目成熟。它们不合用的原因各不相同,而且都是结构性的,不是「做得还不够好」。</span>
<span data-en>All four of these exist and all four are more mature than this project. Each is a bad fit for a different, structural reason — not for want of polish.</span>
</p>
<div class="tw">
<table>
<tr>
<th><span data-zh>方案</span><span data-en>Option</span></th>
<th><span data-zh>为什么不合用</span><span data-en>Why it does not fit</span></th>
</tr>
<tr>
<td><span data-zh>现金悬赏平台</span><span data-en>Cash bounty platforms</span></td>
<td><span data-zh>结构上要求 issue 和仓库是公开的。你的私有业务代码从第一步就进不去,钱多钱少无关。</span><span data-en>Structurally require the issue and the repository to be public. Your private business code cannot get past step one, regardless of the money involved.</span></td>
</tr>
<tr>
<td><span data-zh>外包 / 自由职业平台</span><span data-en>Freelance marketplaces</span></td>
<td><span data-zh>能处理私有代码,代价是签 NDA 加交出完整仓库访问权。对一个半小时的活来说,走流程的摩擦比活本身还大。</span><span data-en>They handle private code, at the price of an NDA plus full repository access. For a ninety-minute task the process costs more than the task.</span></td>
</tr>
<tr>
<td><span data-zh>额度池 / 账号合租</span><span data-en>Quota pools and shared seats</span></td>
<td><span data-zh>共享凭证或代为路由请求,明确违反条款。这正是本项目开头第一段就砍掉的方案。</span><span data-en>Sharing credentials or routing requests on someone's behalf is explicitly prohibited. This is the design this project discarded in its first paragraph.</span></td>
</tr>
<tr>
<td><span data-zh>时间银行 / 通用互助</span><span data-en>Time banks and generic mutual aid</span></td>
<td><span data-zh>换的是小时数,完全不碰「我没法让你看我的代码」这个具体障碍——而那恰好是唯一真正卡住人的地方。</span><span data-en>They exchange hours and never touch the specific obstacle — "I cannot let you see my code" — which happens to be the only thing actually blocking people.</span></td>
</tr>
</table>
</div>
<p class="small muted">
<span data-zh>所以剩下的空位很窄:私有代码 + 小任务 + 不涉及现金 + 不共享访问权。这个项目就想填这一格,别的不碰。</span>
<span data-en>Which leaves a narrow gap: private code, small tasks, no cash, no shared access. That single cell is what this project is trying to fill, and nothing else.</span>
</p>
</section>
<section id="status">
<h2><span data-zh>现在到哪一步了</span><span data-en>Where this actually is</span></h2>
<p>
<span data-zh>项目处在 Phase 0:手工跑几个真任务,看看到底有没有人来。第一批任务发布 3 小时内收到第一个 PR。</span>
<span data-en>The project is in Phase 0: run a few real tasks by hand and find out whether anyone shows up. The first batch got its first pull request within three hours.</span>
</p>
<div class="stat-row">
<div class="stat"><span class="n">2h48m</span><div class="k"><span data-zh>首个交付</span><span data-en>to first delivery</span></div></div>
<div class="stat"><span class="n">5 / 5</span><div class="k"><span data-zh>任务已结算</span><span data-en>tasks settled</span></div></div>
<div class="stat"><span class="n">0</span><div class="k"><span data-zh>业务代码外泄</span><span data-en>lines of business logic exposed</span></div></div>
</div>
<p>
<span data-zh>五个任务全部完成,两位交付者,零返工。其中那个 P1 任务的交付者<strong>只看得到接口和测试</strong>,看不到任何业务代码,实现是正确的,九个验收测试全过,而且没有削弱任何一条测试。这是整个项目最关键的假设,它成立了。</span>
<span data-en>All five tasks delivered, by two people, with no rework. On the P1 task the person <strong>could see only interfaces and tests</strong> — no business code at all — and got it right: nine acceptance tests passing, none of them weakened. That was the project's load-bearing assumption, and it held.</span>
</p>
<div class="callout">
<div class="label"><span data-zh>也别只讲好听的</span><span data-en>The other half</span></div>
<p class="small">
<span data-zh>同一批任务也立刻引来了刷悬赏的机器人:有账号在 24 秒内「接单」四个任务然后一个 PR 都没提,有账号把同一段推销话术重复贴五遍,还有人直接来报美元价。速率限制和禁止无人值守接单这两条规则,就是为这种情况写的——现在它们要开始真的干活了。</span>
<span data-en>The same batch immediately attracted bounty-farming bots: one account "claimed" four tasks in twenty-four seconds and never opened a single PR; another pasted the same pitch five times; someone quoted a price in dollars. The rate limits and the ban on unattended claiming exist for exactly this, and they are about to earn their keep.</span>
</p>
</div>
<h3><span data-zh>第一天就赔掉了一个人的一晚上</span><span data-en>Day one cost someone an evening</span></h3>
<p class="small">
<span data-zh>板子上线第一天,7 号任务收到了两份各自独立、各自正确的实现,相隔两小时,九个验收测试都全过。只能付一个人。第二个人的一晚上白花了——而且他没有任何办法提前知道,因为 issue 上没有任何东西写着「有人在做」。那是板子的错,不是他的错。</span>
<span data-en>On the board's first day, task #7 received two independent, independently correct implementations two hours apart. Both passed all nine acceptance tests. Only one could be paid. The second person's evening was wasted — and there was no way for them to know, because nothing on the issue said it was taken. That was the board's fault, not theirs.</span>
</p>
<p class="small">
<span data-zh>所以接单现在是一个<strong>前置条件</strong>而不是一句声明:<code>/claim</code> 是申请,指派才是答复,<strong>issue 分配给你之前不要动手</strong>。这不是官僚流程,这是唯一挡在贡献者和白干之间的东西。</span>
<span data-en>So claiming is now a <strong>precondition</strong>, not an announcement: <code>/claim</code> is a request, the assignment is the answer, and <strong>you do not start until the issue is assigned to you</strong>. That is not bureaucracy — it is the only thing standing between a contributor and wasted work.</span>
</p>
<h3><span data-zh>第一天实况:24 小时,8 个账号</span><span data-en>Day one, hour by hour</span></h3>
<p class="small">
<span data-zh>板子开张 24 小时内的完整经过,全部可以从公开仓库里逐条核到:每个 issue、每条评论、每个 PR、每一笔账本记录。时间戳是 UTC。</span>
<span data-en>Everything that happened in the board's first 24 hours, each of it checkable line by line in the public repository: every issue, comment, pull request, and ledger entry. Times are UTC.</span>
</p>
<ol class="tl">
<li>
<time>00:06</time>
<p>
<span data-zh><span class="who">mxx1111</span> 在 11 秒内连开 <b>#1–#5</b>。新人的 50 TP 全部进托管。</span>
<span data-en><span class="who">mxx1111</span> opens <b>#1–#5</b> within eleven seconds. All fifty newcomer points go into escrow.</span>
</p>
</li>
<li class="bad">
<time>00:08</time>
<p>
<span data-zh>第一个任务贴出 <b>97 秒</b>后,<span class="who">OpensrcLord</span> 用 <b>24 秒</b>(00:08:19→00:08:43)把 <code>/claim</code>、<code>/attempt</code> 打满 <b>#1–#5 全部五个</b>,每条都说「正在读代码,马上提 PR」。此后一个 PR 都没有。</span>
<span data-en>Ninety-seven seconds after the first task went up, <span class="who">OpensrcLord</span> spends <b>24 seconds</b> (00:08:19→00:08:43) firing <code>/claim</code> and <code>/attempt</code> at <b>all five</b> of #1–#5, each promising a PR "shortly". Not one followed.</span>
</p>
</li>
<li class="bad">
<time>00:09</time>
<p>
<span data-zh>再过 <b>61 秒</b>,<span class="who">OpensrcLord</span> 在<b>本仓库</b>开了 PR #6:40 行,加了一个 <code>.github/workflows/ci.yml</code>,正文写「Closes #5」。但 #5 是 mdlook 的任务——这是把别人仓库的 CI 配置提进了任务板自己。没有可合并的东西,14 小时后按 not-planned 关闭。</span>
<span data-en>Sixty-one seconds later <span class="who">OpensrcLord</span> opens PR #6 <b>against this repository</b>: forty lines adding a <code>.github/workflows/ci.yml</code>, body reading "Closes #5". But #5 was a task about the mdlook repo — this filed another project's CI config into the task board itself. Nothing to merge; closed as not-planned fourteen hours later.</span>
</p>
</li>
<li>
<time>00:20</time>
<p>
<span data-zh><span class="who">mxx1111</span> 撤销 <b>#2</b>:remote-code-mobile 已停止维护,10 TP 退回。此刻还没有人提 PR——这是运气,不是规则拦住的(见 07:38)。</span>
<span data-en><span class="who">mxx1111</span> withdraws <b>#2</b>: remote-code-mobile is unmaintained, 10 TP refunded. No pull request existed yet — that was luck, not a rule (see 07:38).</span>
</p>
</li>
<li class="bad">
<time>02:18–02:34</time>
<p>
<span data-zh><span class="who">chfr19820610-cell</span> 把同一段「我有丰富经验,请把这个悬赏指给我」在 #1、#3、#4 上<b>每 5 分钟重发一轮</b>,共 <b>12 条</b>。之后在 #5、#7 又补了 3 条,全天合计 <b>15 条</b>,零交付。这不是热情,是脚本。</span>
<span data-en><span class="who">chfr19820610-cell</span> re-pastes the same "I have strong experience, please assign me this bounty" across #1, #3 and #4 <b>every five minutes</b> — <b>twelve comments</b>. Three more follow on #5 and #7, <b>fifteen for the day</b>, zero deliveries. Not enthusiasm; a script.</span>
</p>
</li>
<li class="ok">
<time>02:54</time>
<p>
<span data-zh><span class="who">ghzhost</span> 在 mdlook 提交 <b>PR #17</b>(任务 #5):lint、类型检查、构建、dependabot 检查。<b>第一个真实交付,距发单 2 小时 48 分。</b></span>
<span data-en><span class="who">ghzhost</span> opens <b>PR #17</b> on mdlook (task #5): lint, type-check, build, dependabot checks. <b>The first real delivery, 2 h 48 m after posting.</b></span>
</p>
</li>
<li class="ok">
<time>03:45</time>
<p>
<span data-zh><span class="who">manav8498</span> 提交 <a href="https://github.com/mxx1111/Homelab/pull/1">Homelab PR #1</a>(任务 #1),201 行、4 个文件。他<b>全程没在 issue 里说过一句话</b>——fork、写完、提交,是唯一一个不靠评论沟通完成交付的人。</span>
<span data-en><span class="who">manav8498</span> opens <a href="https://github.com/mxx1111/Homelab/pull/1">Homelab PR #1</a> (task #1), 201 lines across four files. He <b>never commented on the issue at all</b> — fork, build, deliver. The only person who completed a delivery without a single word of discussion.</span>
</p>
</li>
<li class="ok">
<time>03:56</time>
<p>
<span data-zh><span class="who">ghzhost</span> 交付 <b>#4</b>:Homelab 的 ntfy 推送渠道。</span>
<span data-en><span class="who">ghzhost</span> delivers <b>#4</b>: the ntfy notification channel on Homelab.</span>
</p>
</li>
<li class="bad">
<time>04:27</time>
<p>
<span data-zh><span class="who">Rithikmahadev12</span> 在 #5 上报价:「My estimate for this work is <b>$10 USD</b>… Please confirm that this matches your expectations before I begin.」触碰红线 3——这里不出现货币定价。无下文。</span>
<span data-en><span class="who">Rithikmahadev12</span> quotes on #5: "My estimate for this work is <b>$10 USD</b>… Please confirm that this matches your expectations before I begin." Red line 3 — nothing here is priced in money. It goes no further.</span>
</p>
</li>
<li class="ok">
<time>04:30</time>
<p>
<span data-zh><span class="who">ghzhost</span> 交付 <b>#3</b>:platform-limits 过期检查 CLI + 定时 CI 告警。</span>
<span data-en><span class="who">ghzhost</span> delivers <b>#3</b>: the platform-limits staleness CLI plus scheduled CI alerting.</span>
</p>
</li>
<li>
<time>06:03</time>
<p>
<span data-zh><span class="who">mxx1111</span> 补发 <b>#7</b>(Express 错误中间件泄露内部错误文本),托管 10 TP。</span>
<span data-en><span class="who">mxx1111</span> posts <b>#7</b> (an Express error handler leaking internal messages), escrowing 10 TP.</span>
</p>
</li>
<li class="ok">
<time>06:07</time>
<p>
<span data-zh><b>4 分钟后</b>,<span class="who">ghzhost</span> 对 #7 发出 <code>/done</code>,附 <a href="https://github.com/mxx1111/task-express-error-handler/pull/1">task-express-error-handler PR #1</a>,带逐行审阅声明。九个验收测试全过。</span>
<span data-en><b>Four minutes later</b> <span class="who">ghzhost</span> posts <code>/done</code> on #7 with <a href="https://github.com/mxx1111/task-express-error-handler/pull/1">task-express-error-handler PR #1</a> and the attestation. All nine acceptance tests pass.</span>
</p>
</li>
<li class="bad">
<time>07:38</time>
<p>
<span data-zh><span class="who">ghzhost</span> 完成 <b>#2</b> 的全部验收标准,在 remote-code-mobile 上开了 PR #6——但那个任务 <b>7 小时 18 分钟前就撤销了</b>,TP 早已退回。<b>一晚上,白干。</b></span>
<span data-en><span class="who">ghzhost</span> meets every acceptance criterion of <b>#2</b> and opens PR #6 on remote-code-mobile — the task had been withdrawn <b>7 h 18 m earlier</b> and its points already refunded. <b>An evening, spent for nothing.</b></span>
</p>
</li>
<li class="bad">
<time>08:10</time>
<p>
<span data-zh><span class="who">davidmoserai</span> 对 <b>#7</b> 发出 <code>/done</code>,实现同样正确,比 ghzhost <b>晚 2 小时 03 分</b>。只能付一个人,PR 未合并即关闭。</span>
<span data-en><span class="who">davidmoserai</span> posts <code>/done</code> on <b>#7</b> — also correct, <b>2 h 03 m behind</b> ghzhost. Only one could be paid; the PR is closed unmerged.</span>
</p>
</li>
<li class="ok">
<time>10:20–11:14</time>
<p>
<span data-zh>逐条核对验收标准后,<span class="who">mxx1111</span> 在 54 分钟里结清五笔(账本 seq 9–13,时间取自 <code>ledger.jsonl</code>):<b>#7</b>→ghzhost、<b>#5</b>→ghzhost、<b>#4</b>→ghzhost、<b>#1</b>→manav8498、<b>#3</b>→ghzhost,各 10 TP。</span>
<span data-en>Acceptance criteria checked line by line, then <span class="who">mxx1111</span> settles all five in fifty-four minutes (ledger seq 9–13, times from <code>ledger.jsonl</code>): <b>#7</b>→ghzhost, <b>#5</b>→ghzhost, <b>#4</b>→ghzhost, <b>#1</b>→manav8498, <b>#3</b>→ghzhost, 10 TP each.</span>
</p>
</li>
<li>
<time>10:30</time>
<p>
<span data-zh>认领规则宣布生效并写进 GOVERNANCE.md:<b>指派之前不要动手。</b>00:08 的五连抢和 02:18 的 12 条重发,从这一刻起才算违规——在此之前板子上没有任何东西说得清「这个有人在做」。</span>
<span data-en>The claim rule takes effect and goes into GOVERNANCE.md: <b>do not start before the issue is assigned to you.</b> The five-claim burst at 00:08 and the twelve re-posts at 02:18 only count as violations from here on — before this, nothing on the board could say "this one is taken".</span>
</p>
</li>
<li class="bad">
<time>11:04</time>
<p>
<span data-zh><span class="who">macakii327-prog</span> 提交 <a href="https://github.com/mxx1111/wechat-mp-writer-skill-mxx/pull/3">wechat-mp-writer-skill PR #3</a>:210 行、4 个文件,完整解决 #3。<b>10 分钟后 #3 结算给了 ghzhost。</b>他没接过单也没评论过,规则上拿不到 TP——但规则公布于 34 分钟前,他不可能看到。<b>第二个白干的人,而且三小时没收到任何回应。</b></span>
<span data-en><span class="who">macakii327-prog</span> opens <a href="https://github.com/mxx1111/wechat-mp-writer-skill-mxx/pull/3">wechat-mp-writer-skill PR #3</a>: 210 lines across four files, a complete answer to #3. <b>Ten minutes later #3 settles to ghzhost.</b> He never claimed and never commented, so under the rules he gets nothing — but the rule had been posted thirty-four minutes earlier and he could not have seen it. <b>The second wasted evening, and three hours of silence before anyone replied.</b></span>
</p>
</li>
<li>
<time>14:18–14:35</time>
<p>
<span data-zh>收尾:<b>#1</b> 补关(结算时忘了关)、<b>PR #6</b> 按 not-planned 关闭、<b>#8</b> 开出——直接问 ghzhost 和 manav8498 <b>当初为什么接单</b>。这是 Phase 0 三个问题里唯一还没有答案的一个。</span>
<span data-en>Cleanup: <b>#1</b> closed retroactively (forgotten at settlement), <b>PR #6</b> closed as not-planned, and <b>#8</b> opened — asking ghzhost and manav8498 outright <b>why they took the tasks</b>. It is the one Phase 0 question still without an answer.</span>
</p>
</li>
</ol>
<p class="small muted">
<span data-zh>24 小时里:5 个任务结算、50 TP 花出去、2 个人拿到钱、<b>2 个人白干</b>、1 个人差点白干、3 个账号只留了字。板子转起来了,规则全部是被撞出来的——这正是 Phase 0 想要的结果,代价是两个陌生人的晚上。</span>
<span data-en>In 24 hours: five tasks settled, fifty points spent, two people paid, <b>two people working for nothing</b>, one person almost working for nothing, three accounts that only left words. The board is turning and every rule on it was written by a collision — which is exactly what Phase 0 was for, at a cost of two strangers' evenings.</span>
</p>
<h3><span data-zh>账本上的位置</span><span data-en>Where everyone stands</span></h3>
<p class="small">
<span data-zh>余额就是 <code>npm run ledger</code> 重算出来的那个数,不是维护者填的表格。交付、白干、只留字——三种参与方式都列出来,因为后两种也是板子真实运转的一部分。</span>
<span data-en>The balances are exactly what <code>npm run ledger</code> recomputes — not a table the maintainer fills in. Delivered, wasted, words-only: all three ways of showing up are listed, because the latter two are part of the board actually running.</span>
</p>
<div class="lb">
<div class="tw">
<table class="num">
<tr><th><span data-zh>#</span><span data-en>#</span></th><th><span data-zh>账号</span><span data-en>Account</span></th><th><span data-zh>任务</span><span data-en>Tasks</span></th><th>TP</th><th><span data-zh>状态</span><span data-en>Standing</span></th></tr>
<tr>
<td><span class="pos first">1</span></td>
<td><a href="https://github.com/ghzhost">@ghzhost</a></td>
<td>#5 · #4 · #3 · #7</td>
<td>40</td>
<td><span data-zh><span class="tag delivered">交付 ×4</span>另有一次白干(#2,任务撤销后才交付)</span><span data-en><span class="tag delivered">delivered ×4</span>plus one wasted run (#2, after withdrawal)</span></td>
</tr>
<tr>
<td><span class="pos">2</span></td>
<td><a href="https://github.com/manav8498">@manav8498</a></td>
<td>#1</td>
<td>10</td>
<td><span data-zh><span class="tag delivered">交付 ×1</span></span><span data-en><span class="tag delivered">delivered ×1</span></span></td>
</tr>
<tr>
<td><span class="pos">3</span></td>
<td><a href="https://github.com/mxx1111">@mxx1111</a></td>
<td><span data-zh>发布者 · 5 单</span><span data-en>poster · 5 tasks</span></td>
<td>0</td>
<td><span data-zh><span class="tag words">发单者</span>50 TP 托管支出,全部结算给接单方</span><span data-en><span class="tag words">poster</span>50 TP of escrow spent, all settled to workers</span></td>
</tr>
</table>
</div>
<div class="tw">
<table class="num">
<tr><th><span data-zh>账号</span><span data-en>Account</span></th><th><span data-zh>动作</span><span data-en>Moved</span></th><th>TP</th><th><span data-zh>结果</span><span data-en>Outcome</span></th></tr>
<tr>
<td><a href="https://github.com/OpensrcLord">@OpensrcLord</a></td>
<td><span data-zh>24 秒连抢 5 单 + 1 个提错仓库的 PR</span><span data-en>5 claims in 24 s + 1 PR to the wrong repo</span></td>
<td>—</td>
<td><span data-zh><span class="tag words">只留字</span>零交付,PR #6 按 not-planned 关闭</span><span data-en><span class="tag words">words</span>zero deliveries, PR #6 closed as not-planned</span></td>
</tr>
<tr>
<td><a href="https://github.com/chfr19820610-cell">@chfr19820610-cell</a></td>
<td><span data-zh>15 条同文案评论</span><span data-en>15 identical comments</span></td>
<td>—</td>
<td><span data-zh><span class="tag words">只留字</span>零交付,已折叠</span><span data-en><span class="tag words">words</span>zero deliveries, minimized</span></td>
</tr>
<tr>
<td><a href="https://github.com/davidmoserai">@davidmoserai</a></td>
<td><span data-zh>交付 #7,晚 2h03m</span><span data-en>delivered #7, 2 h 03 m late</span></td>
<td>—</td>
<td><span data-zh><span class="tag wasted">差点白干</span>先到先得,PR 未合并</span><span data-en><span class="tag wasted">narrow miss</span>first to deliver wins, PR unmerged</span></td>
</tr>
<tr>
<td><a href="https://github.com/macakii327-prog">@macakii327-prog</a></td>
<td><span data-zh>交付 #3,210 行,晚 10 分钟</span><span data-en>delivered #3, 210 lines, 10 min late</span></td>
<td>—</td>
<td><span data-zh><span class="tag wasted">白干</span>从未认领,规则 34 分钟前才公布</span><span data-en><span class="tag wasted">wasted</span>never claimed; the rule was 34 min old</span></td>
</tr>
<tr>
<td><a href="https://github.com/Rithikmahadev12">@Rithikmahadev12</a></td>
<td><span data-zh>报价 $10 USD</span><span data-en>quoted $10 USD</span></td>
<td>—</td>
<td><span data-zh><span class="tag words">只留字</span>触碰红线 3,无下文</span><span data-en><span class="tag words">words</span>red line 3, no further movement</span></td>
</tr>
</table>
</div>
<p class="note">
<span data-zh>合计:发放 100 TP,结算 50 TP,托管中 50(3 个开放任务)。账本 17 条记录,9 条不变量全过。发放额从 50 涨到 100,是维护者为给板子供资发放的一笔——<a href="https://github.com/mxx1111/spare-cycles/issues/12">规则改动</a>与<a href="https://github.com/mxx1111/spare-cycles/blob/main/GOVERNANCE-LOG.md">全过程</a>都公开记录,那笔钱当天全部进了托管,维护者余额仍是 0。</span>
<span data-en>Total: 100 TP issued, 50 TP settled, 50 in escrow across three open tasks. Seventeen ledger entries, all nine invariants holding. Issuance rose from 50 to 100 because the maintainer funded the board — the <a href="https://github.com/mxx1111/spare-cycles/issues/12">rule change</a> and <a href="https://github.com/mxx1111/spare-cycles/blob/main/GOVERNANCE-LOG.md">the whole of it</a> are on the record, all of it reached escrow the same day, and the maintainer balance is still zero.</span>
</p>
</div>
<p class="small muted">
<span data-zh>sparepack 本身是 0.1.x,测试很密(80 个,含一个塞满凭证和客户数据的端到端仓库),但真实用户还很少。开发过程中被自己的测试抓到过两个真实泄露,第三个存在的概率不低。请把人工复核当成保护你的那一层,而不是工具。</span>
<span data-en>sparepack is 0.1.x. The test suite is thorough — 80 tests, including an end-to-end run against a repo seeded with credentials and customer records — but few people have used it on their own code yet. Two real leaks were caught by those tests during development; the odds of a third are not small. Treat the manifest review as the thing keeping you safe, not the tool.</span>
</p>
</section>
<section id="faq">
<h2><span data-zh>常见问题</span><span data-en>Questions</span></h2>
<div class="faq">
<details>
<summary><span data-zh>我只想用 sparepack,不想加入什么社区,行吗?</span><span data-en>Can I just use sparepack and skip the community?</span></summary>
<div class="a"><p>
<span data-zh>行。sparepack 是独立的 npm 包,MIT 协议,跟任务板零耦合,也不会往任何地方上传东西。想让人帮你改代码又不想交出整个代码库——这件事本身不需要一个社区。</span>
<span data-en>Yes. sparepack is a standalone npm package under MIT, with no coupling to the board and no network calls. You do not need a community to want help with your code without handing over the codebase.</span>
</p></div>
</details>
<details>
<summary><span data-zh>积分能换钱吗?</span><span data-en>Can points be converted to money?</span></summary>
<div class="a"><p>
<span data-zh>不能,而且永远不会能。不可转让、不可交易、不可提现、不是证券也不是货币。一旦可转让,立刻会出现拿现金买积分的灰市,这个项目的性质当场变成无监管的劳务撮合。所以它不是一句政策,是账本 schema 里压根没有那个交易类型。</span>
<span data-en>No, and never. Not transferable, not tradable, not redeemable, not a security and not a currency. The moment they were transferable a grey market in points would appear and this would become unregulated labour brokerage. So it is not a policy — the transaction type simply does not exist in the ledger schema.</span>
</p></div>
</details>
<details>
<summary><span data-zh>我这周额度用完了,还能参与吗?</span><span data-en>I'm out of quota this week. Can I still take part?</span></summary>
<div class="a"><p>
<span data-zh>能,而且这是刻意设计的。审别人的交付、复核别人的脱敏产出、替别人写验收测试、当争议仲裁——四类任务都不烧任何 AI 额度,照样赚分。原始设计里有个死锁:最缺积分的人恰恰是最重度、最没余力赚积分的人,这条通道就是为了解开它。</span>
<span data-en>Yes, and that is deliberate. Reviewing deliveries, auditing someone's redaction output, writing acceptance tests, arbitrating disputes — four task types that burn no AI quota and still earn points. The original design had a deadlock: the people who most need points are the heavy users least able to earn them. This channel exists to break it.</span>
</p></div>
</details>
<details>
<summary><span data-zh>交付的代码归谁?</span><span data-en>Who owns the delivered code?</span></summary>
<div class="a"><p>
<span data-zh>PR 提在<em>你自己的</em>仓库里,按你仓库的 license 走。这个项目从头到尾不碰、不托管、不中转你的代码——任务元数据、账本和信誉记录在公开仓库里,代码只在你和接单者的仓库之间流转。这一条同时也大幅削减了本项目的法律责任面。</span>
<span data-en>The PR lands in <em>your</em> repository under your repository's licence. This project never touches, hosts, or relays your code — task metadata, the ledger, and reputation live in the public repo; the code moves only between your repo and the worker's. That also keeps this project's liability surface small.</span>
</p></div>
</details>
<details>
<summary><span data-zh>这算不算违反 Anthropic 条款?</span><span data-en>Does this violate Anthropic's terms?</span></summary>
<div class="a">
<p><span data-zh>交付的是<strong>产出</strong>不是<strong>访问权</strong>。用自己的订阅为别人干活并交付成果,就是普通的自由职业,消费者条款并不禁止商业用途。界线就在这里,五条红线全部围绕它。</span>
<span data-en>What is delivered is <strong>output</strong>, not <strong>access</strong>. Using your own subscription to do work for someone and handing over the result is ordinary freelancing; the Consumer Terms do not prohibit commercial use. That is the line, and all five red lines sit around it.</span></p>
<p><span data-zh>但这是一个由开发者而非律师运营的项目。你的账号由你自己负责,用雇主提供的 seat 的话先问过雇主——很多企业协议有额外限制。</span>
<span data-en>But this is run by developers, not lawyers. You are responsible for your own account, and if you are on an employer-provided seat, ask them first — many enterprise agreements add their own restrictions.</span></p>
</div>
</details>
<details>
<summary><span data-zh>我怎么知道扫描器没漏东西?</span><span data-en>How do I know the scanner didn't miss something?</span></summary>
<div class="a"><p>
<span data-zh>你不知道,我也不知道。词法扫描抓不到含义。所以工具的设计不是「扫干净了就发」,而是<strong>写盘前强制你看一遍完整文件清单并逐字敲确认</strong>。工具的作用是把那次复核变得可能——列清楚发了哪些文件、多大、哪些函数被丢弃了——而不是替你复核。另外 <code>sparepack verify</code> 会从磁盘完全重新推导一遍,不信任任何清单,因为和被检查对象共享假设的检查等于没检查。</span>
<span data-en>You do not, and neither do I. Lexical scanning cannot catch meaning. So the tool is not built around "scan clean, then ship" — it is built around <strong>forcing you to read the full file list and type a confirmation before anything is written</strong>. Its job is to make that review possible (what shipped, how big, which functions were dropped), not to do it for you. Separately, <code>sparepack verify</code> re-derives everything from disk and trusts no manifest, because a check that shares assumptions with the thing it checks catches nothing.</span>
</p></div>
</details>
<details>
<summary><span data-zh>能不能让 OpenClaw 这类 agent 挂个定时任务,自动接单赚积分?</span><span data-en>Can I have an OpenClaw-style agent poll the board and auto-claim tasks?</span></summary>
<div class="a">
<p><span data-zh>不能,这正是红线 4:由活人读任务、决定接、干活、在提交前亲自审阅。让 agent <strong>盯着任务流提醒你</strong>完全可以——「有个 M 档的 TypeScript 任务,要看看吗」是个很好的 cron 任务。但 <code>/claim</code> 那一下必须是你按的,交付前那一遍必须是你看的。</span>
<span data-en>No — that is red line 4 verbatim: a human reads the task, decides to take it, does the work, and reviews it before submitting. Having an agent <strong>watch the feed and notify you</strong> is completely fine — "there's an M-tier TypeScript task, want a look?" is a great cron job. But the <code>/claim</code> has to be yours, and so does the review before delivery.</span></p>
<p><span data-zh>机制上也刷不动:<code>/claim</code> 只是申请,维护者指派了才算数,速率限制是同时 2 个、每周 5 个。被识别出的无人值守接单,按治理规则是接单作废加停用 30 天。想让 agent 全自动接活赚钱的话,有专为 agent 设计的悬赏市场,那是另一个前提下的另一种东西——这个板子是给人用的。</span>
<span data-en>Mechanically it does not work either: <code>/claim</code> is a request, only the maintainer's assignment counts, and the limits are two concurrent, five a week. Unattended claiming, when identified, voids the claims and suspends the account for 30 days. If you want an agent earning autonomously, bounty markets built for agents exist — that is a different thing built on the opposite premise. This board is for people.</span></p>
</div>
</details>