claude-dev-env 1.83.0 → 1.84.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 (165) hide show
  1. package/_shared/pr-loop/audit-contract.md +24 -12
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  4. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
  5. package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
  6. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
  7. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
  8. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
  9. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
  10. package/agents/clean-coder.md +3 -0
  11. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  12. package/bin/install.mjs +16 -8
  13. package/bin/install.test.mjs +17 -6
  14. package/commands/CLAUDE.md +0 -1
  15. package/docs/CODE_RULES.md +1 -1
  16. package/hooks/blocking/CLAUDE.md +0 -4
  17. package/hooks/blocking/code_rules_constants_config.py +5 -0
  18. package/hooks/blocking/code_rules_docstrings.py +116 -6
  19. package/hooks/blocking/code_rules_enforcer.py +4 -0
  20. package/hooks/blocking/code_rules_magic_values.py +5 -0
  21. package/hooks/blocking/code_rules_naming_collection.py +11 -6
  22. package/hooks/blocking/code_rules_shared.py +34 -0
  23. package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
  24. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
  25. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
  26. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
  27. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
  28. package/hooks/blocking/test_code_rules_magic_values.py +54 -0
  29. package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
  30. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
  31. package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
  32. package/hooks/hooks_constants/CLAUDE.md +0 -3
  33. package/hooks/hooks_constants/blocking_check_limits.py +3 -0
  34. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  35. package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
  36. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
  37. package/hooks/validation/post_tool_use_dispatcher.py +1 -1
  38. package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
  39. package/hooks/workflow/CLAUDE.md +2 -8
  40. package/package.json +1 -1
  41. package/rules/CLAUDE.md +1 -1
  42. package/rules/plain-illustrative-docstrings.md +35 -1
  43. package/skills/CLAUDE.md +6 -1
  44. package/skills/autoconverge/SKILL.md +38 -57
  45. package/skills/autoconverge/reference/closing-report.md +6 -6
  46. package/skills/autoconverge/reference/convergence.md +17 -15
  47. package/skills/autoconverge/reference/gotchas.md +6 -3
  48. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
  49. package/skills/autoconverge/workflow/converge.contract.test.mjs +87 -13
  50. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +1 -1
  51. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
  52. package/skills/autoconverge/workflow/converge.mjs +70 -73
  53. package/skills/autoconverge/workflow/render_report.py +7 -11
  54. package/skills/bugteam/CLAUDE.md +1 -1
  55. package/skills/bugteam/PROMPTS.md +7 -6
  56. package/skills/bugteam/SKILL.md +25 -72
  57. package/skills/bugteam/reference/CLAUDE.md +1 -3
  58. package/skills/bugteam/reference/README.md +1 -1
  59. package/skills/bugteam/reference/audit-and-teammates.md +1 -1
  60. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
  61. package/skills/bugteam/reference/team-setup.md +8 -7
  62. package/skills/bugteam/scripts/CLAUDE.md +0 -6
  63. package/skills/bugteam/scripts/README.md +0 -4
  64. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
  65. package/skills/code/SKILL.md +2 -0
  66. package/skills/copilot-review/CLAUDE.md +1 -1
  67. package/skills/copilot-review/SKILL.md +25 -23
  68. package/skills/findbugs/CLAUDE.md +2 -2
  69. package/skills/findbugs/SKILL.md +22 -83
  70. package/skills/fixbugs/SKILL.md +2 -4
  71. package/skills/log-audit/CLAUDE.md +20 -0
  72. package/skills/log-audit/SKILL.md +68 -0
  73. package/skills/log-audit/reference/CLAUDE.md +9 -0
  74. package/skills/log-audit/reference/charter.md +52 -0
  75. package/skills/log-audit/scripts/CLAUDE.md +27 -0
  76. package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
  77. package/skills/log-audit/scripts/collect_log_window.py +199 -0
  78. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
  79. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
  80. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
  81. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
  82. package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
  83. package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
  84. package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
  85. package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
  86. package/skills/monitor-open-prs/SKILL.md +2 -2
  87. package/skills/post-audit-findings/SKILL.md +84 -0
  88. package/skills/pr-converge/CLAUDE.md +2 -0
  89. package/skills/pr-converge/SKILL.md +72 -59
  90. package/skills/pr-converge/reference/CLAUDE.md +1 -1
  91. package/skills/pr-converge/reference/convergence-gates.md +16 -19
  92. package/skills/pr-converge/reference/examples.md +5 -5
  93. package/skills/pr-converge/reference/fix-protocol.md +16 -43
  94. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
  95. package/skills/pr-converge/reference/per-tick.md +24 -45
  96. package/skills/pr-converge/reference/state-schema.md +15 -0
  97. package/skills/pr-converge/scripts/README.md +3 -5
  98. package/skills/pr-fix-protocol/SKILL.md +70 -0
  99. package/skills/pr-loop-lifecycle/SKILL.md +73 -0
  100. package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
  101. package/skills/pr-scope-resolve/SKILL.md +48 -0
  102. package/skills/qbug/CLAUDE.md +4 -4
  103. package/skills/qbug/SKILL.md +46 -144
  104. package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
  105. package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
  106. package/skills/reviewer-gates/SKILL.md +96 -0
  107. package/skills/session-log/CLAUDE.md +7 -7
  108. package/skills/session-log/SKILL.md +27 -44
  109. package/skills/test_markdown_link_integrity.py +103 -0
  110. package/commands/doc-gist.md +0 -16
  111. package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
  112. package/hooks/blocking/conftest.py +0 -30
  113. package/hooks/blocking/md_path_exemptions.py +0 -224
  114. package/hooks/blocking/md_to_html_blocker.py +0 -155
  115. package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
  116. package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
  117. package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
  118. package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
  119. package/hooks/hooks_constants/html_companion_constants.py +0 -20
  120. package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
  121. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
  122. package/hooks/workflow/doc_gist_auto_publish.py +0 -144
  123. package/hooks/workflow/md_to_html_companion.py +0 -358
  124. package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
  125. package/hooks/workflow/test_md_to_html_companion.py +0 -613
  126. package/skills/bugteam/reference/audit-contract.md +0 -163
  127. package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
  128. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
  129. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
  130. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
  131. package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
  132. package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
  133. package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
  134. package/skills/doc-gist/CLAUDE.md +0 -25
  135. package/skills/doc-gist/SKILL.md +0 -97
  136. package/skills/doc-gist/references/CLAUDE.md +0 -9
  137. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
  138. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
  139. package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
  140. package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
  141. package/skills/doc-gist/references/examples/05-design-system.html +0 -629
  142. package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
  143. package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
  144. package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
  145. package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
  146. package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
  147. package/skills/doc-gist/references/examples/11-status-report.html +0 -528
  148. package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
  149. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
  150. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
  151. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
  152. package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
  153. package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
  154. package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
  155. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
  156. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
  157. package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
  158. package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
  159. package/skills/doc-gist/references/examples/README.md +0 -5
  160. package/skills/doc-gist/scripts/CLAUDE.md +0 -27
  161. package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
  162. package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
  163. package/skills/doc-gist/scripts/gist_upload.py +0 -177
  164. package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
  165. /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
@@ -1,573 +0,0 @@
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>Birchline — Cycle 14 triage</title>
7
- <style>
8
- :root {
9
- --ivory: #FAF9F5;
10
- --slate: #141413;
11
- --clay: #D97757;
12
- --clay-d: #B85C3E;
13
- --oat: #E3DACC;
14
- --olive: #788C5D;
15
- --gray-50: #F0EEE6;
16
- --gray-200: #D1CFC5;
17
- --gray-500: #87867F;
18
- --gray-800: #3D3D3A;
19
- --white: #ffffff;
20
- --serif: ui-serif, Georgia, "Times New Roman", serif;
21
- --sans: system-ui, -apple-system, "Segoe UI", sans-serif;
22
- --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
23
- }
24
- * { box-sizing: border-box; }
25
- html, body {
26
- margin: 0;
27
- background: var(--ivory);
28
- color: var(--slate);
29
- font-family: var(--sans);
30
- font-size: 14px;
31
- line-height: 1.5;
32
- }
33
- .wrap {
34
- max-width: 1180px;
35
- margin: 0 auto;
36
- padding: 48px 32px 64px;
37
- }
38
-
39
- /* ---------- header ---------- */
40
- header { margin-bottom: 22px; }
41
- .eyebrow {
42
- font-family: var(--mono);
43
- font-size: 11px;
44
- letter-spacing: 0.08em;
45
- text-transform: uppercase;
46
- color: var(--gray-500);
47
- }
48
- h1 {
49
- font-family: var(--serif);
50
- font-weight: 500;
51
- font-size: 30px;
52
- letter-spacing: -0.01em;
53
- margin: 6px 0 4px;
54
- }
55
- .sub {
56
- color: var(--gray-500);
57
- max-width: 620px;
58
- margin: 0;
59
- }
60
- .hintline {
61
- font-family: var(--mono);
62
- font-size: 11px;
63
- color: var(--gray-500);
64
- margin-top: 10px;
65
- }
66
- .hintline::before {
67
- content: "›";
68
- color: var(--clay);
69
- margin-right: 6px;
70
- }
71
-
72
- /* ---------- toolbar ---------- */
73
- .toolbar {
74
- position: sticky;
75
- top: 0;
76
- z-index: 5;
77
- background: var(--ivory);
78
- display: flex;
79
- align-items: center;
80
- gap: 14px;
81
- padding: 14px 0 12px;
82
- margin-bottom: 14px;
83
- border-bottom: 1.5px solid var(--gray-200);
84
- }
85
- .summary {
86
- font-family: var(--mono);
87
- font-size: 12px;
88
- color: var(--gray-800);
89
- letter-spacing: 0.01em;
90
- }
91
- .summary b { color: var(--slate); font-weight: 600; }
92
- .summary .dot { color: var(--gray-200); margin: 0 4px; }
93
- .spacer { flex: 1; }
94
- .filter-active {
95
- font-family: var(--mono);
96
- font-size: 11px;
97
- color: var(--clay-d);
98
- background: #F5E6DE;
99
- border: 1.5px solid #E8C9BA;
100
- border-radius: 999px;
101
- padding: 4px 10px;
102
- cursor: pointer;
103
- display: none;
104
- }
105
- .filter-active.on { display: inline-block; }
106
- .filter-active:hover { border-color: var(--clay); }
107
- button.primary {
108
- background: var(--slate);
109
- color: var(--ivory);
110
- border: 1.5px solid var(--slate);
111
- border-radius: 999px;
112
- font-family: var(--mono);
113
- font-size: 12px;
114
- padding: 9px 16px;
115
- cursor: pointer;
116
- letter-spacing: 0.01em;
117
- }
118
- button.primary:hover { background: var(--gray-800); }
119
- button.primary.copied { background: var(--olive); border-color: var(--olive); }
120
- button.ghost {
121
- background: transparent;
122
- color: var(--gray-800);
123
- border: 1.5px solid var(--gray-200);
124
- border-radius: 999px;
125
- font-family: var(--mono);
126
- font-size: 12px;
127
- padding: 9px 16px;
128
- cursor: pointer;
129
- }
130
- button.ghost:hover { border-color: var(--gray-500); }
131
-
132
- /* ---------- board ---------- */
133
- .board {
134
- display: grid;
135
- grid-template-columns: repeat(4, 1fr);
136
- gap: 16px;
137
- align-items: start;
138
- }
139
- .col {
140
- background: var(--white);
141
- border: 1.5px solid var(--gray-200);
142
- border-radius: 12px;
143
- overflow: hidden;
144
- display: flex;
145
- flex-direction: column;
146
- min-height: 200px;
147
- }
148
- .col[data-col="now"] { border-top: 3px solid var(--clay); }
149
- .col[data-col="next"] { border-top: 3px solid var(--olive); }
150
- .col[data-col="later"] { border-top: 3px solid var(--gray-500); }
151
- .col[data-col="cut"] { border-top: 3px solid var(--gray-200); }
152
- .col.dragover {
153
- outline: 2px dashed var(--clay);
154
- outline-offset: -6px;
155
- background: #FBF6F2;
156
- }
157
- .col-head {
158
- position: sticky;
159
- top: 0;
160
- z-index: 1;
161
- background: var(--white);
162
- display: flex;
163
- align-items: baseline;
164
- gap: 8px;
165
- padding: 14px 14px 10px;
166
- border-bottom: 1.5px solid var(--gray-50);
167
- }
168
- .col-head h2 {
169
- font-family: var(--serif);
170
- font-weight: 500;
171
- font-size: 17px;
172
- margin: 0;
173
- letter-spacing: -0.01em;
174
- }
175
- .col-head .count {
176
- margin-left: auto;
177
- font-family: var(--mono);
178
- font-size: 11px;
179
- color: var(--gray-500);
180
- background: var(--gray-50);
181
- border: 1.5px solid var(--gray-200);
182
- border-radius: 999px;
183
- padding: 1px 8px;
184
- min-width: 26px;
185
- text-align: center;
186
- }
187
- .col-body {
188
- padding: 10px 10px 6px;
189
- display: flex;
190
- flex-direction: column;
191
- gap: 8px;
192
- flex: 1;
193
- min-height: 60px;
194
- }
195
- .col-foot {
196
- border-top: 1.5px solid var(--gray-50);
197
- padding: 8px 14px 12px;
198
- font-family: var(--mono);
199
- font-size: 11px;
200
- color: var(--gray-500);
201
- display: flex;
202
- justify-content: space-between;
203
- }
204
- .col-foot b { color: var(--gray-800); font-weight: 600; }
205
-
206
- /* ---------- ticket card ---------- */
207
- .ticket {
208
- background: var(--white);
209
- border: 1.5px solid var(--gray-200);
210
- border-radius: 8px;
211
- padding: 10px 11px 9px;
212
- cursor: grab;
213
- user-select: none;
214
- transition: border-color 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
215
- }
216
- .ticket:hover { border-color: var(--gray-500); box-shadow: 0 1px 3px rgba(20,20,19,0.06); }
217
- .ticket:active { cursor: grabbing; }
218
- .ticket.dragging { opacity: .4; }
219
- .ticket.dim { opacity: .25; }
220
- .ticket-top {
221
- display: flex;
222
- align-items: center;
223
- gap: 8px;
224
- margin-bottom: 5px;
225
- }
226
- .tid {
227
- font-family: var(--mono);
228
- font-size: 11px;
229
- color: var(--gray-500);
230
- letter-spacing: 0.01em;
231
- }
232
- .tag {
233
- font-family: var(--mono);
234
- font-size: 10px;
235
- text-transform: uppercase;
236
- letter-spacing: 0.05em;
237
- border-radius: 999px;
238
- padding: 1px 7px 2px;
239
- border: 1px solid transparent;
240
- cursor: pointer;
241
- }
242
- .tag:hover { filter: brightness(0.96); }
243
- .tag-bug { background: #F5E2D8; color: var(--clay-d); border-color: #E8C9BA; }
244
- .tag-feat { background: #E8EDE0; color: #5C6F44; border-color: #CFDAC0; }
245
- .tag-chore { background: var(--gray-50); color: var(--gray-800); border-color: var(--gray-200); }
246
- .tag-debt { background: var(--gray-50); color: var(--gray-800); border-color: var(--gray-200); }
247
- .est {
248
- margin-left: auto;
249
- font-family: var(--mono);
250
- font-size: 10px;
251
- color: var(--gray-500);
252
- border: 1.5px solid var(--gray-200);
253
- border-radius: 4px;
254
- width: 18px;
255
- height: 18px;
256
- display: flex;
257
- align-items: center;
258
- justify-content: center;
259
- }
260
- .ttitle {
261
- font-size: 13px;
262
- line-height: 1.35;
263
- color: var(--slate);
264
- margin-bottom: 7px;
265
- }
266
- .ticket-bot {
267
- display: flex;
268
- align-items: center;
269
- gap: 6px;
270
- }
271
- .owner {
272
- font-family: var(--mono);
273
- font-size: 10px;
274
- width: 20px;
275
- height: 20px;
276
- border-radius: 50%;
277
- background: var(--oat);
278
- color: var(--gray-800);
279
- display: flex;
280
- align-items: center;
281
- justify-content: center;
282
- letter-spacing: 0.02em;
283
- }
284
- .owner.o2 { background: #DCE4D2; }
285
- .owner.o3 { background: #ECD9CE; }
286
- .owner.o4 { background: var(--gray-50); }
287
-
288
- @media (max-width: 920px) {
289
- .board { grid-template-columns: repeat(2, 1fr); }
290
- }
291
- </style>
292
- </head>
293
- <body>
294
- <div class="wrap">
295
-
296
- <header>
297
- <div class="eyebrow">Birchline / editor / triage</div>
298
- <h1>Cycle 14 triage</h1>
299
- <p class="sub">
300
- Twenty-four open Linear tickets, pre-sorted into a best guess. Drag them
301
- across Now / Next / Later / Cut until the cut feels right, then copy the
302
- result back into the planning doc as markdown.
303
- </p>
304
- <div class="hintline">drag tickets between columns &nbsp;·&nbsp; click a tag to filter</div>
305
- </header>
306
-
307
- <div class="toolbar">
308
- <div class="summary" id="summary"></div>
309
- <div class="spacer"></div>
310
- <button class="filter-active" id="filterBadge"></button>
311
- <button class="ghost" id="resetBtn">Reset</button>
312
- <button class="primary" id="copyBtn">Copy as markdown</button>
313
- </div>
314
-
315
- <div class="board" id="board"></div>
316
-
317
- </div>
318
-
319
- <script>
320
- // ---------- data ----------
321
- var COLUMNS = [
322
- { key: 'now', label: 'Now', rationale: 'Blocking the v2.4 release or actively losing user data.' },
323
- { key: 'next', label: 'Next', rationale: 'High-leverage and well-scoped — start the moment Now clears.' },
324
- { key: 'later', label: 'Later', rationale: 'Real, but can ride to a future cycle without anyone noticing.' },
325
- { key: 'cut', label: 'Cut', rationale: 'Not this quarter — close, dedupe, or push back to the requester.' }
326
- ];
327
-
328
- var EST_PTS = { S: 1, M: 2, L: 3 };
329
-
330
- // tag, est, owner, col
331
- var INITIAL = [
332
- { id: 'BIR-241', title: 'Fix sync conflict toast firing twice on reconnect', tag: 'bug', est: 'M', owner: 'AK', col: 'now' },
333
- { id: 'BIR-238', title: 'Comments lost when editing offline then reloading', tag: 'bug', est: 'L', owner: 'JM', col: 'now' },
334
- { id: 'BIR-252', title: 'Billing webhook 500s on annual → monthly downgrade', tag: 'bug', est: 'M', owner: 'RS', col: 'now' },
335
- { id: 'BIR-219', title: 'Migrate workspace permissions to new ACL table', tag: 'debt', est: 'L', owner: 'AK', col: 'now' },
336
- { id: 'BIR-260', title: 'SSO login loop on Safari 17 with strict cookies', tag: 'bug', est: 'S', owner: 'TN', col: 'now' },
337
-
338
- { id: 'BIR-244', title: 'Inline @-mention picker in doc comments', tag: 'feat', est: 'M', owner: 'JM', col: 'next' },
339
- { id: 'BIR-231', title: 'Bulk-archive completed projects from the sidebar', tag: 'feat', est: 'S', owner: 'EL', col: 'next' },
340
- { id: 'BIR-247', title: 'Activity feed: collapse repeated edit events', tag: 'feat', est: 'M', owner: 'TN', col: 'next' },
341
- { id: 'BIR-228', title: 'Notification preferences per project', tag: 'feat', est: 'L', owner: 'EL', col: 'next' },
342
- { id: 'BIR-255', title: 'Keyboard shortcut cheat-sheet overlay (⌘/)', tag: 'feat', est: 'S', owner: 'RS', col: 'next' },
343
- { id: 'BIR-236', title: 'Empty-state illustrations for new workspaces', tag: 'chore', est: 'S', owner: 'EL', col: 'next' },
344
- { id: 'BIR-249', title: 'Audit log export to CSV for admins', tag: 'feat', est: 'M', owner: 'AK', col: 'next' },
345
-
346
- { id: 'BIR-213', title: 'Dark mode pass on settings + billing pages', tag: 'chore', est: 'M', owner: 'EL', col: 'later' },
347
- { id: 'BIR-258', title: 'Slack integration: post on milestone close', tag: 'feat', est: 'M', owner: 'RS', col: 'later' },
348
- { id: 'BIR-221', title: 'Replace moment.js with date-fns across web', tag: 'debt', est: 'L', owner: 'TN', col: 'later' },
349
- { id: 'BIR-263', title: 'Drag-to-reorder columns on the board view', tag: 'feat', est: 'M', owner: 'JM', col: 'later' },
350
- { id: 'BIR-209', title: 'Upgrade Postgres minor + reindex search vectors', tag: 'chore', est: 'M', owner: 'AK', col: 'later' },
351
- { id: 'BIR-251', title: 'Per-doc read receipts in the share dialog', tag: 'feat', est: 'L', owner: 'JM', col: 'later' },
352
- { id: 'BIR-240', title: 'Onboarding checklist resurfaces after dismissal', tag: 'bug', est: 'S', owner: 'TN', col: 'later' },
353
- { id: 'BIR-265', title: 'Add request-id to client error reports', tag: 'debt', est: 'S', owner: 'RS', col: 'later' },
354
-
355
- { id: 'BIR-198', title: 'Custom emoji reactions on comments', tag: 'feat', est: 'M', owner: 'EL', col: 'cut' },
356
- { id: 'BIR-204', title: 'Native Windows desktop wrapper', tag: 'feat', est: 'L', owner: 'TN', col: 'cut' },
357
- { id: 'BIR-217', title: 'AI summary of long comment threads', tag: 'feat', est: 'L', owner: 'JM', col: 'cut' },
358
- { id: 'BIR-233', title: 'Public roadmap page with voting', tag: 'feat', est: 'L', owner: 'RS', col: 'cut' }
359
- ];
360
-
361
- var OWNER_CLASS = { AK: 'o1', JM: 'o2', RS: 'o3', TN: 'o4', EL: 'o1' };
362
-
363
- var tickets = []; // live state
364
- var activeFilter = null; // tag string or null
365
- var dragId = null;
366
-
367
- // ---------- build DOM ----------
368
- var board = document.getElementById('board');
369
- var colBodies = {};
370
- var colCounts = {};
371
- var colFoots = {};
372
-
373
- COLUMNS.forEach(function (c) {
374
- var col = document.createElement('section');
375
- col.className = 'col';
376
- col.dataset.col = c.key;
377
-
378
- var head = document.createElement('div');
379
- head.className = 'col-head';
380
- var h2 = document.createElement('h2');
381
- h2.textContent = c.label;
382
- var count = document.createElement('span');
383
- count.className = 'count';
384
- head.appendChild(h2);
385
- head.appendChild(count);
386
-
387
- var body = document.createElement('div');
388
- body.className = 'col-body';
389
-
390
- var foot = document.createElement('div');
391
- foot.className = 'col-foot';
392
-
393
- col.appendChild(head);
394
- col.appendChild(body);
395
- col.appendChild(foot);
396
- board.appendChild(col);
397
-
398
- colBodies[c.key] = body;
399
- colCounts[c.key] = count;
400
- colFoots[c.key] = foot;
401
-
402
- // drop targets
403
- col.addEventListener('dragover', function (e) {
404
- e.preventDefault();
405
- e.dataTransfer.dropEffect = 'move';
406
- col.classList.add('dragover');
407
- });
408
- col.addEventListener('dragleave', function (e) {
409
- if (!col.contains(e.relatedTarget)) col.classList.remove('dragover');
410
- });
411
- col.addEventListener('drop', function (e) {
412
- e.preventDefault();
413
- col.classList.remove('dragover');
414
- if (!dragId) return;
415
- var t = tickets.find(function (x) { return x.id === dragId; });
416
- if (t && t.col !== c.key) { t.col = c.key; render(); }
417
- });
418
- });
419
-
420
- function makeCard(t) {
421
- var card = document.createElement('article');
422
- card.className = 'ticket';
423
- card.draggable = true;
424
- card.dataset.id = t.id;
425
- if (activeFilter && t.tag !== activeFilter) card.classList.add('dim');
426
-
427
- var top = document.createElement('div');
428
- top.className = 'ticket-top';
429
- var tid = document.createElement('span');
430
- tid.className = 'tid';
431
- tid.textContent = t.id;
432
- var tag = document.createElement('button');
433
- tag.type = 'button';
434
- tag.className = 'tag tag-' + t.tag;
435
- tag.textContent = t.tag;
436
- tag.addEventListener('click', function (e) {
437
- e.stopPropagation();
438
- activeFilter = (activeFilter === t.tag) ? null : t.tag;
439
- render();
440
- });
441
- var est = document.createElement('span');
442
- est.className = 'est';
443
- est.textContent = t.est;
444
- top.appendChild(tid);
445
- top.appendChild(tag);
446
- top.appendChild(est);
447
-
448
- var title = document.createElement('div');
449
- title.className = 'ttitle';
450
- title.textContent = t.title;
451
-
452
- var bot = document.createElement('div');
453
- bot.className = 'ticket-bot';
454
- var owner = document.createElement('span');
455
- owner.className = 'owner ' + (OWNER_CLASS[t.owner] || 'o1');
456
- owner.textContent = t.owner;
457
- bot.appendChild(owner);
458
-
459
- card.appendChild(top);
460
- card.appendChild(title);
461
- card.appendChild(bot);
462
-
463
- card.addEventListener('dragstart', function (e) {
464
- dragId = t.id;
465
- card.classList.add('dragging');
466
- e.dataTransfer.effectAllowed = 'move';
467
- e.dataTransfer.setData('text/plain', t.id);
468
- });
469
- card.addEventListener('dragend', function () {
470
- dragId = null;
471
- card.classList.remove('dragging');
472
- document.querySelectorAll('.col.dragover').forEach(function (el) {
473
- el.classList.remove('dragover');
474
- });
475
- });
476
-
477
- return card;
478
- }
479
-
480
- function render() {
481
- COLUMNS.forEach(function (c) { colBodies[c.key].innerHTML = ''; });
482
- var counts = {};
483
- var pts = {};
484
- COLUMNS.forEach(function (c) { counts[c.key] = 0; pts[c.key] = 0; });
485
-
486
- tickets.forEach(function (t) {
487
- colBodies[t.col].appendChild(makeCard(t));
488
- counts[t.col] += 1;
489
- pts[t.col] += EST_PTS[t.est] || 0;
490
- });
491
-
492
- COLUMNS.forEach(function (c) {
493
- colCounts[c.key].textContent = counts[c.key];
494
- colFoots[c.key].innerHTML = 'estimate <b>' + pts[c.key] + ' pt' + (pts[c.key] === 1 ? '' : 's') + '</b>';
495
- });
496
-
497
- var summary = document.getElementById('summary');
498
- summary.innerHTML = COLUMNS.map(function (c) {
499
- return '<b>' + counts[c.key] + '</b>&nbsp;' + c.key;
500
- }).join('<span class="dot">·</span>');
501
-
502
- var badge = document.getElementById('filterBadge');
503
- if (activeFilter) {
504
- badge.textContent = 'filter: ' + activeFilter + ' ×';
505
- badge.classList.add('on');
506
- } else {
507
- badge.classList.remove('on');
508
- }
509
- }
510
-
511
- document.getElementById('filterBadge').addEventListener('click', function () {
512
- activeFilter = null;
513
- render();
514
- });
515
-
516
- // ---------- export ----------
517
- function buildMarkdown() {
518
- var lines = [];
519
- lines.push('# Birchline — Cycle 14 triage');
520
- lines.push('');
521
- COLUMNS.forEach(function (c) {
522
- var rows = tickets.filter(function (t) { return t.col === c.key; });
523
- var pts = rows.reduce(function (s, t) { return s + (EST_PTS[t.est] || 0); }, 0);
524
- lines.push('## ' + c.label + ' (' + rows.length + ' · ' + pts + ' pts)');
525
- lines.push('');
526
- lines.push('_' + c.rationale + '_');
527
- lines.push('');
528
- rows.forEach(function (t) {
529
- lines.push('- **' + t.id + '** ' + t.title + ' — ' + t.tag + ', ' + t.est + ', ' + t.owner);
530
- });
531
- lines.push('');
532
- });
533
- return lines.join('\n');
534
- }
535
-
536
- var copyBtn = document.getElementById('copyBtn');
537
- var copyTimer = null;
538
- copyBtn.addEventListener('click', function () {
539
- var md = buildMarkdown();
540
- function flash() {
541
- copyBtn.textContent = 'Copied ✓';
542
- copyBtn.classList.add('copied');
543
- clearTimeout(copyTimer);
544
- copyTimer = setTimeout(function () {
545
- copyBtn.textContent = 'Copy as markdown';
546
- copyBtn.classList.remove('copied');
547
- }, 1200);
548
- }
549
- if (navigator.clipboard && navigator.clipboard.writeText) {
550
- navigator.clipboard.writeText(md).then(flash, flash);
551
- } else {
552
- var ta = document.createElement('textarea');
553
- ta.value = md;
554
- document.body.appendChild(ta);
555
- ta.select();
556
- try { document.execCommand('copy'); } catch (e) {}
557
- document.body.removeChild(ta);
558
- flash();
559
- }
560
- });
561
-
562
- document.getElementById('resetBtn').addEventListener('click', function () {
563
- tickets = INITIAL.map(function (t) { return Object.assign({}, t); });
564
- activeFilter = null;
565
- render();
566
- });
567
-
568
- // ---------- init ----------
569
- tickets = INITIAL.map(function (t) { return Object.assign({}, t); });
570
- render();
571
- </script>
572
- </body>
573
- </html>