backpass 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +406 -0
  3. package/bin/backpass.js +4 -0
  4. package/package.json +62 -0
  5. package/src/acpx.js +576 -0
  6. package/src/agents.js +389 -0
  7. package/src/analyze.js +289 -0
  8. package/src/apply/lavish.js +128 -0
  9. package/src/apply/terminal.js +119 -0
  10. package/src/apply/writer.js +101 -0
  11. package/src/bootstrap.js +74 -0
  12. package/src/cli.js +261 -0
  13. package/src/commands/analyze.js +88 -0
  14. package/src/commands/apply.js +103 -0
  15. package/src/commands/bootstrap.js +172 -0
  16. package/src/commands/init.js +59 -0
  17. package/src/commands/propose.js +136 -0
  18. package/src/commands/run.js +95 -0
  19. package/src/commands/scan.js +90 -0
  20. package/src/commands/status.js +143 -0
  21. package/src/commands/usage.js +25 -0
  22. package/src/config.js +249 -0
  23. package/src/diff.js +305 -0
  24. package/src/discovery/adapters/claude.js +77 -0
  25. package/src/discovery/adapters/codex.js +162 -0
  26. package/src/discovery/adapters/cursor-cli.js +109 -0
  27. package/src/discovery/adapters/cursor-ide.js +130 -0
  28. package/src/discovery/adapters/grok.js +107 -0
  29. package/src/discovery/adapters/opencode.js +151 -0
  30. package/src/discovery/adapters/pi.js +87 -0
  31. package/src/discovery/adapters/shared.js +195 -0
  32. package/src/discovery/adapters/sqlite.js +50 -0
  33. package/src/discovery/association.js +100 -0
  34. package/src/discovery/index.js +226 -0
  35. package/src/discovery/self.js +62 -0
  36. package/src/distill.js +182 -0
  37. package/src/fold.js +214 -0
  38. package/src/gap-ledger.js +174 -0
  39. package/src/logger.js +74 -0
  40. package/src/memory.js +244 -0
  41. package/src/progress.js +29 -0
  42. package/src/prompts/analysis.md +48 -0
  43. package/src/prompts/annotate.md +48 -0
  44. package/src/prompts/synthesis.md +98 -0
  45. package/src/prompts.js +36 -0
  46. package/src/proposal.js +430 -0
  47. package/src/redact.js +36 -0
  48. package/src/repo.js +118 -0
  49. package/src/sample.js +99 -0
  50. package/src/skills.js +207 -0
  51. package/src/state.js +202 -0
  52. package/src/subprocess.js +47 -0
  53. package/src/synthesize.js +287 -0
  54. package/src/tokens.js +48 -0
  55. package/src/tui/index.js +336 -0
  56. package/src/tui/render.js +487 -0
  57. package/src/tui/term.js +130 -0
  58. package/src/tui/theme.js +111 -0
  59. package/src/workspace.js +162 -0
  60. package/templates/apply.html +928 -0
@@ -0,0 +1,928 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>backpass apply</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b0e14;
10
+ --panel: #10141d;
11
+ --panel-2: #0e1219;
12
+ --line: #1c2431;
13
+ --line-soft: #161d28;
14
+ --text: #d9dfea;
15
+ --dim: #8a93a5;
16
+ --faint: #5a6375;
17
+ --accent: #4fe3c1;
18
+ --accent-dim: rgba(79, 227, 193, 0.12);
19
+ --reject: #ff5d73;
20
+ --reject-dim: rgba(255, 93, 115, 0.1);
21
+ --defer: #ffc857;
22
+ --defer-dim: rgba(255, 200, 87, 0.1);
23
+ --blue: #6ea8ff;
24
+ --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
25
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
26
+ }
27
+ * {
28
+ box-sizing: border-box;
29
+ }
30
+ html {
31
+ background: var(--bg);
32
+ }
33
+ body {
34
+ margin: 0;
35
+ background: var(--bg);
36
+ color: var(--text);
37
+ font-family: var(--sans);
38
+ font-size: 15px;
39
+ line-height: 1.55;
40
+ -webkit-font-smoothing: antialiased;
41
+ background-image:
42
+ linear-gradient(rgba(110, 168, 255, 0.025) 1px, transparent 1px),
43
+ linear-gradient(90deg, rgba(110, 168, 255, 0.025) 1px, transparent 1px);
44
+ background-size: 48px 48px;
45
+ }
46
+ .wrap {
47
+ max-width: 1080px;
48
+ margin: 0 auto;
49
+ padding: 48px 28px 140px;
50
+ }
51
+ .microlabel {
52
+ font-family: var(--mono);
53
+ font-size: 10.5px;
54
+ letter-spacing: 0.14em;
55
+ text-transform: uppercase;
56
+ color: var(--faint);
57
+ }
58
+ .num {
59
+ font-variant-numeric: tabular-nums;
60
+ }
61
+
62
+ /* ---------- header ---------- */
63
+ .brand {
64
+ display: flex;
65
+ align-items: baseline;
66
+ gap: 14px;
67
+ margin-bottom: 6px;
68
+ flex-wrap: wrap;
69
+ }
70
+ .brand h1 {
71
+ font-family: var(--mono);
72
+ font-weight: 500;
73
+ font-size: 22px;
74
+ margin: 0;
75
+ letter-spacing: 0.01em;
76
+ }
77
+ .brand h1 .cmd {
78
+ color: var(--accent);
79
+ }
80
+ .brand .ver {
81
+ font-family: var(--mono);
82
+ font-size: 11.5px;
83
+ color: var(--faint);
84
+ }
85
+ .runline {
86
+ font-family: var(--mono);
87
+ font-size: 12px;
88
+ color: var(--dim);
89
+ margin-bottom: 34px;
90
+ word-break: break-word;
91
+ }
92
+ .runline b {
93
+ color: var(--text);
94
+ font-weight: 500;
95
+ }
96
+
97
+ .statrow {
98
+ display: grid;
99
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
100
+ gap: 1px;
101
+ background: var(--line);
102
+ border: 1px solid var(--line);
103
+ margin-bottom: 36px;
104
+ }
105
+ .stat {
106
+ background: var(--panel);
107
+ padding: 14px 16px;
108
+ min-width: 0;
109
+ }
110
+ .stat .v {
111
+ font-family: var(--mono);
112
+ font-size: 21px;
113
+ font-weight: 500;
114
+ color: var(--text);
115
+ }
116
+ .stat .v em {
117
+ font-style: normal;
118
+ color: var(--accent);
119
+ }
120
+ .stat .k {
121
+ font-family: var(--mono);
122
+ font-size: 10.5px;
123
+ letter-spacing: 0.12em;
124
+ text-transform: uppercase;
125
+ color: var(--faint);
126
+ margin-top: 3px;
127
+ }
128
+
129
+ /* ---------- budget gauge ---------- */
130
+ .gauge-block {
131
+ border: 1px solid var(--line);
132
+ background: var(--panel);
133
+ padding: 18px 20px 20px;
134
+ margin-bottom: 44px;
135
+ }
136
+ .gauge-head {
137
+ display: flex;
138
+ justify-content: space-between;
139
+ align-items: baseline;
140
+ flex-wrap: wrap;
141
+ gap: 8px;
142
+ margin-bottom: 12px;
143
+ }
144
+ .gauge-head .t {
145
+ font-size: 13px;
146
+ color: var(--dim);
147
+ }
148
+ .gauge-head .readout {
149
+ font-family: var(--mono);
150
+ font-size: 12.5px;
151
+ color: var(--dim);
152
+ }
153
+ .gauge-head .readout b {
154
+ color: var(--accent);
155
+ font-weight: 500;
156
+ }
157
+ .gauge-head .readout b.over {
158
+ color: var(--reject);
159
+ }
160
+ .gauge {
161
+ position: relative;
162
+ height: 8px;
163
+ background: var(--panel-2);
164
+ border: 1px solid var(--line-soft);
165
+ }
166
+ .gauge .fill-now {
167
+ position: absolute;
168
+ inset: 0 auto 0 0;
169
+ background: linear-gradient(90deg, rgba(110, 168, 255, 0.35), rgba(110, 168, 255, 0.6));
170
+ }
171
+ .gauge .fill-after {
172
+ position: absolute;
173
+ inset: 0 auto 0 0;
174
+ background: linear-gradient(90deg, rgba(79, 227, 193, 0.5), var(--accent));
175
+ transition: width 0.35s ease;
176
+ }
177
+ .gauge .fill-after.over {
178
+ background: linear-gradient(90deg, rgba(255, 93, 115, 0.5), var(--reject));
179
+ }
180
+ .gauge .cap {
181
+ position: absolute;
182
+ top: -5px;
183
+ bottom: -5px;
184
+ left: 100%;
185
+ width: 2px;
186
+ background: var(--reject);
187
+ opacity: 0.8;
188
+ }
189
+ .gauge-legend {
190
+ display: flex;
191
+ gap: 22px;
192
+ margin-top: 10px;
193
+ font-family: var(--mono);
194
+ font-size: 11px;
195
+ color: var(--faint);
196
+ flex-wrap: wrap;
197
+ }
198
+ .gauge-legend i {
199
+ display: inline-block;
200
+ width: 14px;
201
+ height: 5px;
202
+ margin-right: 6px;
203
+ vertical-align: middle;
204
+ }
205
+
206
+ /* ---------- section titles ---------- */
207
+ .sect {
208
+ display: flex;
209
+ align-items: baseline;
210
+ gap: 12px;
211
+ margin: 0 0 18px;
212
+ }
213
+ .sect h2 {
214
+ font-size: 15px;
215
+ font-weight: 600;
216
+ margin: 0;
217
+ letter-spacing: 0.01em;
218
+ }
219
+ .sect .rule {
220
+ flex: 1;
221
+ height: 1px;
222
+ background: var(--line);
223
+ }
224
+ .sect .count {
225
+ font-family: var(--mono);
226
+ font-size: 11.5px;
227
+ color: var(--faint);
228
+ }
229
+
230
+ /* ---------- edit cards ---------- */
231
+ .edit {
232
+ border: 1px solid var(--line);
233
+ background: var(--panel);
234
+ margin-bottom: 22px;
235
+ position: relative;
236
+ }
237
+ .edit::before {
238
+ content: "";
239
+ position: absolute;
240
+ left: 0;
241
+ top: 0;
242
+ bottom: 0;
243
+ width: 2px;
244
+ background: var(--blue);
245
+ }
246
+ .edit[data-kind="remove"]::before {
247
+ background: var(--reject);
248
+ }
249
+ .edit[data-kind="add"]::before {
250
+ background: var(--accent);
251
+ }
252
+ .edit[data-kind="extract"]::before {
253
+ background: var(--defer);
254
+ }
255
+ .edit[data-state="accepted"] {
256
+ border-color: rgba(79, 227, 193, 0.45);
257
+ }
258
+ .edit[data-state="rejected"] {
259
+ opacity: 0.55;
260
+ }
261
+ .edit-head {
262
+ display: flex;
263
+ align-items: center;
264
+ gap: 12px;
265
+ padding: 13px 18px;
266
+ border-bottom: 1px solid var(--line-soft);
267
+ flex-wrap: wrap;
268
+ }
269
+ .kind {
270
+ font-family: var(--mono);
271
+ font-size: 10px;
272
+ letter-spacing: 0.13em;
273
+ padding: 3px 8px;
274
+ border: 1px solid var(--line);
275
+ color: var(--dim);
276
+ white-space: nowrap;
277
+ }
278
+ .edit[data-kind="add"] .kind {
279
+ color: var(--accent);
280
+ border-color: rgba(79, 227, 193, 0.4);
281
+ }
282
+ .edit[data-kind="remove"] .kind {
283
+ color: var(--reject);
284
+ border-color: rgba(255, 93, 115, 0.4);
285
+ }
286
+ .edit[data-kind="rewrite"] .kind {
287
+ color: var(--blue);
288
+ border-color: rgba(110, 168, 255, 0.4);
289
+ }
290
+ .edit[data-kind="extract"] .kind {
291
+ color: var(--defer);
292
+ border-color: rgba(255, 200, 87, 0.4);
293
+ }
294
+ .edit-head .title {
295
+ font-size: 14px;
296
+ font-weight: 550;
297
+ flex: 1;
298
+ min-width: 200px;
299
+ }
300
+ .edit-head .delta {
301
+ font-family: var(--mono);
302
+ font-size: 12px;
303
+ color: var(--dim);
304
+ white-space: nowrap;
305
+ }
306
+ .edit-head .delta.pos {
307
+ color: var(--accent);
308
+ }
309
+ .edit-head .delta.neg {
310
+ color: var(--reject);
311
+ }
312
+ .edit-body {
313
+ padding: 16px 18px 18px;
314
+ }
315
+ .rationale {
316
+ font-size: 13.5px;
317
+ color: var(--dim);
318
+ margin: 0 0 12px;
319
+ }
320
+ .target {
321
+ font-family: var(--mono);
322
+ font-size: 11px;
323
+ color: var(--faint);
324
+ margin-bottom: 10px;
325
+ word-break: break-all;
326
+ }
327
+
328
+ .diff {
329
+ font-family: var(--mono);
330
+ font-size: 12.5px;
331
+ line-height: 1.7;
332
+ border: 1px solid var(--line-soft);
333
+ background: var(--panel-2);
334
+ padding: 12px 14px;
335
+ overflow-x: auto;
336
+ white-space: pre-wrap;
337
+ word-break: break-word;
338
+ margin-bottom: 14px;
339
+ }
340
+ .diff .del {
341
+ display: block;
342
+ color: #c98d97;
343
+ background: var(--reject-dim);
344
+ padding: 1px 8px;
345
+ margin: 1px 0;
346
+ }
347
+ .diff .del::before {
348
+ content: "- ";
349
+ color: var(--reject);
350
+ }
351
+ .diff .ins {
352
+ display: block;
353
+ color: #9fe8d6;
354
+ background: var(--accent-dim);
355
+ padding: 1px 8px;
356
+ margin: 1px 0;
357
+ }
358
+ .diff .ins::before {
359
+ content: "+ ";
360
+ color: var(--accent);
361
+ }
362
+ .diff .ctx {
363
+ display: block;
364
+ color: var(--faint);
365
+ padding: 1px 8px;
366
+ }
367
+ .diff .ctx::before {
368
+ content: " ";
369
+ }
370
+ .diff .gap {
371
+ display: block;
372
+ color: var(--faint);
373
+ padding: 1px 8px;
374
+ border-top: 1px dashed var(--line-soft);
375
+ }
376
+
377
+ .evidence {
378
+ border-top: 1px dashed var(--line-soft);
379
+ padding-top: 12px;
380
+ }
381
+ .evidence summary {
382
+ cursor: pointer;
383
+ font-family: var(--mono);
384
+ font-size: 11.5px;
385
+ color: var(--blue);
386
+ letter-spacing: 0.06em;
387
+ list-style: none;
388
+ }
389
+ .evidence summary::-webkit-details-marker {
390
+ display: none;
391
+ }
392
+ .evidence summary::before {
393
+ content: "> ";
394
+ }
395
+ .evidence[open] summary::before {
396
+ content: "v ";
397
+ }
398
+ .evidence .quote {
399
+ border-left: 2px solid var(--line);
400
+ margin: 10px 0 4px;
401
+ padding: 6px 12px;
402
+ font-size: 12.5px;
403
+ color: var(--dim);
404
+ }
405
+ .evidence .quote .src {
406
+ font-family: var(--mono);
407
+ font-size: 10.5px;
408
+ color: var(--faint);
409
+ display: block;
410
+ margin-top: 4px;
411
+ }
412
+ .evidence .quote.negative {
413
+ border-left-color: var(--reject);
414
+ }
415
+ .evidence .quote.positive {
416
+ border-left-color: var(--accent);
417
+ }
418
+
419
+ .decide {
420
+ display: flex;
421
+ gap: 8px;
422
+ margin-top: 14px;
423
+ }
424
+ .decide button {
425
+ font-family: var(--mono);
426
+ font-size: 11.5px;
427
+ letter-spacing: 0.08em;
428
+ padding: 7px 16px;
429
+ background: transparent;
430
+ border: 1px solid var(--line);
431
+ color: var(--dim);
432
+ cursor: pointer;
433
+ transition: all 0.12s ease;
434
+ }
435
+ .decide button:hover {
436
+ border-color: var(--dim);
437
+ color: var(--text);
438
+ }
439
+ .decide button.on-accept {
440
+ border-color: var(--accent);
441
+ color: var(--accent);
442
+ background: var(--accent-dim);
443
+ }
444
+ .decide button.on-reject {
445
+ border-color: var(--reject);
446
+ color: var(--reject);
447
+ background: var(--reject-dim);
448
+ }
449
+
450
+ .skillprev {
451
+ border: 1px solid var(--line-soft);
452
+ background: var(--panel-2);
453
+ padding: 12px 14px;
454
+ font-family: var(--mono);
455
+ font-size: 12px;
456
+ line-height: 1.65;
457
+ color: var(--dim);
458
+ margin-bottom: 14px;
459
+ white-space: pre-wrap;
460
+ word-break: break-word;
461
+ overflow-x: auto;
462
+ }
463
+ .skillprev .fm {
464
+ color: var(--defer);
465
+ }
466
+
467
+ .notes {
468
+ border: 1px solid var(--line);
469
+ background: var(--panel);
470
+ padding: 16px 20px;
471
+ margin-top: 34px;
472
+ }
473
+ .notes h3 {
474
+ margin: 0 0 8px;
475
+ font-size: 12px;
476
+ font-family: var(--mono);
477
+ letter-spacing: 0.1em;
478
+ text-transform: uppercase;
479
+ color: var(--faint);
480
+ font-weight: 500;
481
+ }
482
+ .notes li {
483
+ font-size: 13px;
484
+ color: var(--dim);
485
+ }
486
+ .empty {
487
+ border: 1px dashed var(--line);
488
+ padding: 28px;
489
+ text-align: center;
490
+ color: var(--dim);
491
+ font-family: var(--mono);
492
+ font-size: 13px;
493
+ }
494
+
495
+ /* ---------- sticky apply bar ---------- */
496
+ .applybar {
497
+ position: fixed;
498
+ left: 0;
499
+ right: 0;
500
+ bottom: 0;
501
+ background: rgba(11, 14, 20, 0.92);
502
+ backdrop-filter: blur(8px);
503
+ border-top: 1px solid var(--line);
504
+ z-index: 50;
505
+ }
506
+ .applybar-in {
507
+ max-width: 1080px;
508
+ margin: 0 auto;
509
+ padding: 14px 28px;
510
+ display: flex;
511
+ align-items: center;
512
+ gap: 22px;
513
+ flex-wrap: wrap;
514
+ }
515
+ .applybar .tally {
516
+ font-family: var(--mono);
517
+ font-size: 12.5px;
518
+ color: var(--dim);
519
+ display: flex;
520
+ gap: 18px;
521
+ flex-wrap: wrap;
522
+ }
523
+ .applybar .tally b {
524
+ font-weight: 500;
525
+ }
526
+ .applybar .tally .a b {
527
+ color: var(--accent);
528
+ }
529
+ .applybar .tally .r b {
530
+ color: var(--reject);
531
+ }
532
+ .applybar .proj {
533
+ font-family: var(--mono);
534
+ font-size: 12px;
535
+ color: var(--faint);
536
+ flex: 1;
537
+ min-width: 180px;
538
+ }
539
+ .applybar .proj b {
540
+ color: var(--text);
541
+ font-weight: 500;
542
+ }
543
+ .applybar .proj b.over {
544
+ color: var(--reject);
545
+ }
546
+ .btn-apply {
547
+ font-family: var(--mono);
548
+ font-size: 12.5px;
549
+ letter-spacing: 0.1em;
550
+ padding: 11px 26px;
551
+ background: var(--accent);
552
+ color: #06231c;
553
+ border: none;
554
+ cursor: pointer;
555
+ font-weight: 600;
556
+ transition: opacity 0.15s;
557
+ }
558
+ .btn-apply:hover {
559
+ opacity: 0.88;
560
+ }
561
+ .btn-apply:disabled {
562
+ background: var(--line);
563
+ color: var(--faint);
564
+ cursor: not-allowed;
565
+ }
566
+
567
+ @media (max-width: 640px) {
568
+ .wrap {
569
+ padding: 28px 16px 170px;
570
+ }
571
+ .applybar-in {
572
+ padding: 12px 16px;
573
+ gap: 12px;
574
+ }
575
+ }
576
+ </style>
577
+ </head>
578
+ <body>
579
+ <div class="wrap">
580
+ <div class="microlabel" style="margin-bottom: 10px">gradient descent for your agent memory</div>
581
+ <div class="brand">
582
+ <h1><span class="cmd">backpass</span> apply</h1>
583
+ <span class="ver" id="ver"></span>
584
+ </div>
585
+ <div class="runline" id="runline"></div>
586
+
587
+ <div class="statrow" id="statrow"></div>
588
+
589
+ <div class="gauge-block">
590
+ <div class="gauge-head">
591
+ <span class="t" id="gauge-title">Always-loaded budget</span>
592
+ <span class="readout num" id="gauge-readout"></span>
593
+ </div>
594
+ <div class="gauge">
595
+ <div class="fill-now" id="gauge-now"></div>
596
+ <div class="fill-after" id="gauge-after"></div>
597
+ <div class="cap"></div>
598
+ </div>
599
+ <div class="gauge-legend">
600
+ <span><i style="background: rgba(110, 168, 255, 0.5)"></i>current</span>
601
+ <span><i style="background: var(--accent)"></i>projected after apply</span>
602
+ <span><i style="background: var(--reject); height: 9px; width: 2px"></i><span id="cap-label"></span></span>
603
+ </div>
604
+ </div>
605
+
606
+ <div class="sect">
607
+ <h2>Proposed edits</h2>
608
+ <div class="rule"></div>
609
+ <span class="count num" id="editcount"></span>
610
+ </div>
611
+ <div id="edits"></div>
612
+
613
+ <div class="notes" id="notes" hidden>
614
+ <h3>Notes from this run</h3>
615
+ <ul id="noteslist" style="margin: 0; padding-left: 18px"></ul>
616
+ </div>
617
+ </div>
618
+
619
+ <div class="applybar">
620
+ <div class="applybar-in">
621
+ <div class="tally num">
622
+ <span class="a"><b id="n-accept">0</b> accept</span>
623
+ <span class="r"><b id="n-reject">0</b> reject</span>
624
+ </div>
625
+ <div class="proj num" id="projline"></div>
626
+ <button class="btn-apply" id="btn-apply" type="button">APPLY</button>
627
+ </div>
628
+ </div>
629
+
630
+ <script>
631
+ (function () {
632
+ "use strict";
633
+
634
+ // The CLI injects exactly one payload; nothing on this page is model-generated.
635
+ var P = window.__BACKPASS_PROPOSAL__ || { edits: [], stats: {}, budget: {}, memoryFile: {}, repo: {} };
636
+ var edits = P.edits || [];
637
+ var budget = P.budget || { current: 0, capTokens: 0 };
638
+ var decisions = {};
639
+ edits.forEach(function (e) {
640
+ decisions[e.id] = "accepted";
641
+ });
642
+
643
+ function fmt(n) {
644
+ return Number(n || 0).toLocaleString("en-US");
645
+ }
646
+ function el(tag, cls, text) {
647
+ var n = document.createElement(tag);
648
+ if (cls) n.className = cls;
649
+ if (text !== undefined && text !== null) n.textContent = String(text);
650
+ return n;
651
+ }
652
+ function pct(value, cap) {
653
+ return cap > 0 ? Math.min(100, (value / cap) * 100) : 0;
654
+ }
655
+
656
+ // ---- header ----
657
+ document.getElementById("ver").textContent =
658
+ "v" + (P.toolVersion || "0.1.0") + " · run " + (P.generatedAt || "").replace(/\.\d+Z$/, "Z");
659
+
660
+ var harnessBits = Object.keys(P.stats.harnessCounts || {}).map(function (h) {
661
+ return h + " " + P.stats.harnessCounts[h];
662
+ });
663
+ var runline = document.getElementById("runline");
664
+ runline.appendChild(el("b", null, P.repo.name || "repo"));
665
+ runline.appendChild(document.createTextNode(" · " + (P.memoryFile.path || "") + " · analyzed "));
666
+ runline.appendChild(el("b", null, fmt(P.stats.transcripts)));
667
+ runline.appendChild(
668
+ document.createTextNode(" transcripts" + (harnessBits.length ? " (" + harnessBits.join(" · ") + ")" : "")),
669
+ );
670
+
671
+ // ---- stats ----
672
+ var stats = [
673
+ [
674
+ String(edits.length) + "/" + String((P.config || {}).maxEditsPerRun || edits.length),
675
+ "edits proposed / cap",
676
+ null,
677
+ ],
678
+ [fmt(P.stats.positive), "positive evidence", "accent"],
679
+ [fmt(P.stats.negative), "negative evidence", "reject"],
680
+ [fmt(P.stats.gapClusters), "gap clusters", "defer"],
681
+ [fmt(P.stats.skillExtractions), "skill extractions", null],
682
+ ];
683
+ var statrow = document.getElementById("statrow");
684
+ stats.forEach(function (s) {
685
+ var box = el("div", "stat");
686
+ var v = el("div", "v num", s[0]);
687
+ box.appendChild(v);
688
+ box.appendChild(el("div", "k", s[1]));
689
+ statrow.appendChild(box);
690
+ });
691
+
692
+ // ---- budget gauge ----
693
+ document.getElementById("gauge-title").textContent = "Always-loaded budget · " + (P.memoryFile.path || "");
694
+ document.getElementById("cap-label").textContent = fmt(budget.capTokens) + " tok cap";
695
+ document.getElementById("gauge-now").style.width = pct(budget.current, budget.capTokens) + "%";
696
+
697
+ // ---- edit cards ----
698
+ var host = document.getElementById("edits");
699
+ document.getElementById("editcount").textContent =
700
+ edits.length + " of " + ((P.config || {}).maxEditsPerRun || edits.length) + " (learning-rate cap)";
701
+
702
+ if (!edits.length) {
703
+ host.appendChild(
704
+ el("div", "empty", "No edits proposed. The evidence did not clear the thresholds this run."),
705
+ );
706
+ }
707
+
708
+ edits.forEach(function (edit) {
709
+ var card = el("article", "edit");
710
+ card.setAttribute("data-kind", edit.kind);
711
+ card.setAttribute("data-state", "accepted");
712
+
713
+ var head = el("div", "edit-head");
714
+ head.appendChild(el("span", "kind", edit.kind === "extract" ? "EXTRACT -> SKILL" : edit.kind.toUpperCase()));
715
+ head.appendChild(el("span", "title", edit.title));
716
+ var delta = edit.deltaTokens || 0;
717
+ var deltaNode = el(
718
+ "span",
719
+ "delta num " + (delta < 0 ? "pos" : delta > 0 ? "neg" : ""),
720
+ "Δ " +
721
+ (delta > 0 ? "+" : "") +
722
+ fmt(delta) +
723
+ " tok" +
724
+ (edit.targetsMemoryFile ? "" : " (not always-loaded)"),
725
+ );
726
+ head.appendChild(deltaNode);
727
+ card.appendChild(head);
728
+
729
+ var body = el("div", "edit-body");
730
+ if (!edit.targetsMemoryFile) body.appendChild(el("div", "target", "target: " + edit.file));
731
+ if (edit.rationale) body.appendChild(el("p", "rationale", edit.rationale));
732
+
733
+ if (edit.kind === "extract" && edit.skill) {
734
+ var prev = el("div", "skillprev");
735
+ prev.appendChild(
736
+ el(
737
+ "span",
738
+ "fm",
739
+ "--- " +
740
+ edit.skill.path +
741
+ " (new) ---\nname: " +
742
+ edit.skill.name +
743
+ "\ndescription: " +
744
+ edit.skill.description,
745
+ ),
746
+ );
747
+ prev.appendChild(document.createTextNode("\n\n" + (edit.skill.body || "").slice(0, 900)));
748
+ body.appendChild(prev);
749
+ }
750
+
751
+ var diff = el("div", "diff");
752
+ diffLines(edit).forEach(function (line) {
753
+ diff.appendChild(el("span", line.type, line.text));
754
+ });
755
+ body.appendChild(diff);
756
+
757
+ if ((edit.evidence || []).length) {
758
+ var det = el("details", "evidence");
759
+ var counts = { positive: 0, negative: 0 };
760
+ edit.evidence.forEach(function (q) {
761
+ counts[q.polarity === "positive" ? "positive" : "negative"] += 1;
762
+ });
763
+ det.appendChild(
764
+ el(
765
+ "summary",
766
+ null,
767
+ "evidence · " +
768
+ (edit.transcripts || 0) +
769
+ " transcripts (" +
770
+ counts.negative +
771
+ "-, " +
772
+ counts.positive +
773
+ "+)",
774
+ ),
775
+ );
776
+ edit.evidence.forEach(function (q) {
777
+ var quote = el(
778
+ "div",
779
+ "quote " + (q.polarity === "positive" ? "positive" : "negative"),
780
+ "“" + q.text + "”",
781
+ );
782
+ quote.appendChild(el("span", "src", q.source));
783
+ det.appendChild(quote);
784
+ });
785
+ body.appendChild(det);
786
+ }
787
+
788
+ var decide = el("div", "decide");
789
+ ["accept", "reject"].forEach(function (choice) {
790
+ var btn = el("button", choice === "accept" ? "on-accept" : "", choice.toUpperCase());
791
+ btn.type = "button";
792
+ btn.addEventListener("click", function () {
793
+ decisions[edit.id] = choice === "accept" ? "accepted" : "rejected";
794
+ card.setAttribute("data-state", decisions[edit.id]);
795
+ Array.prototype.forEach.call(decide.children, function (b) {
796
+ b.className = "";
797
+ });
798
+ btn.className = "on-" + choice;
799
+ recompute();
800
+ });
801
+ decide.appendChild(btn);
802
+ });
803
+ body.appendChild(decide);
804
+ card.appendChild(body);
805
+ host.appendChild(card);
806
+ });
807
+
808
+ function nonEmpty(line) {
809
+ return line.length > 0;
810
+ }
811
+
812
+ // Measured edits carry display lines per hunk (ctx/del/ins); a legacy
813
+ // find/replace edit is shown as removed then added lines.
814
+ function diffLines(edit) {
815
+ if (Array.isArray(edit.hunks)) {
816
+ var out = [];
817
+ edit.hunks.forEach(function (hunk, i) {
818
+ if (i > 0) out.push({ type: "gap", text: "\u2026" });
819
+ (hunk.lines || []).forEach(function (line) {
820
+ out.push(line);
821
+ });
822
+ });
823
+ return out;
824
+ }
825
+ return (edit.find || "")
826
+ .split("\n")
827
+ .filter(nonEmpty)
828
+ .map(function (text) {
829
+ return { type: "del", text: text };
830
+ })
831
+ .concat(
832
+ (edit.replace || "")
833
+ .split("\n")
834
+ .filter(nonEmpty)
835
+ .map(function (text) {
836
+ return { type: "ins", text: text };
837
+ }),
838
+ );
839
+ }
840
+
841
+ // ---- notes ----
842
+ if ((P.notes || []).length) {
843
+ document.getElementById("notes").hidden = false;
844
+ var list = document.getElementById("noteslist");
845
+ P.notes.forEach(function (n) {
846
+ list.appendChild(el("li", null, n));
847
+ });
848
+ }
849
+
850
+ // ---- live tally + projection ----
851
+ function recompute() {
852
+ var accepted = 0,
853
+ rejected = 0,
854
+ tok = budget.current;
855
+ edits.forEach(function (e) {
856
+ if (decisions[e.id] === "accepted") {
857
+ accepted += 1;
858
+ if (e.targetsMemoryFile) tok += e.deltaTokens || 0;
859
+ } else if (decisions[e.id] === "rejected") {
860
+ rejected += 1;
861
+ }
862
+ });
863
+ var over = tok > budget.capTokens;
864
+
865
+ document.getElementById("n-accept").textContent = accepted;
866
+ document.getElementById("n-reject").textContent = rejected;
867
+
868
+ var readout = document.getElementById("gauge-readout");
869
+ readout.textContent = "";
870
+ readout.appendChild(document.createTextNode("now " + fmt(budget.current) + " tok -> if accepted "));
871
+ // In shrink mode the file started over budget: progress is the goal, not arrival.
872
+ readout.appendChild(
873
+ el("b", (budget.mode === "shrink" ? tok >= budget.current : over) ? "over" : null, fmt(tok) + " tok"),
874
+ );
875
+ readout.appendChild(
876
+ document.createTextNode(
877
+ " / " +
878
+ fmt(budget.capTokens) +
879
+ " cap" +
880
+ (budget.mode === "shrink"
881
+ ? " · shrink plan: " + fmt(Math.max(0, tok - budget.capTokens)) + " tok still over"
882
+ : ""),
883
+ ),
884
+ );
885
+
886
+ var after = document.getElementById("gauge-after");
887
+ after.style.width = pct(tok, budget.capTokens) + "%";
888
+ after.className = "fill-after" + (over ? " over" : "");
889
+
890
+ var proj = document.getElementById("projline");
891
+ proj.textContent = "";
892
+ proj.appendChild(document.createTextNode("projected: "));
893
+ proj.appendChild(el("b", over ? "over" : null, fmt(tok)));
894
+ proj.appendChild(document.createTextNode(" / " + fmt(budget.capTokens) + " tok"));
895
+
896
+ var btn = document.getElementById("btn-apply");
897
+ btn.textContent =
898
+ accepted > 0 ? "APPLY " + accepted + " EDIT" + (accepted === 1 ? "" : "S") + " ->" : "NOTHING TO APPLY";
899
+ btn.disabled = accepted === 0 && rejected === 0;
900
+ }
901
+
902
+ // ---- the single structured decision vector back to the CLI ----
903
+ document.getElementById("btn-apply").addEventListener("click", function () {
904
+ var vector = edits
905
+ .map(function (e) {
906
+ return e.id + "=" + decisions[e.id];
907
+ })
908
+ .join(" ");
909
+ var message = "BACKPASS_DECISIONS " + vector;
910
+ if (window.lavish && typeof window.lavish.queuePrompt === "function") {
911
+ window.lavish.queuePrompt(message, {
912
+ tag: "choice",
913
+ text: vector,
914
+ data: { question: "backpass-apply-decisions", decisions: decisions },
915
+ queueKey: "backpass-apply-decisions",
916
+ });
917
+ this.textContent = "QUEUED - SEND FROM THE PANEL";
918
+ } else {
919
+ this.textContent = "QUEUED (offline preview)";
920
+ }
921
+ this.disabled = true;
922
+ });
923
+
924
+ recompute();
925
+ })();
926
+ </script>
927
+ </body>
928
+ </html>