claude-dev-env 1.83.0 → 1.85.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 (186) 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/pr_loop_shared_constants/CLAUDE.md +1 -1
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +8 -0
  6. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
  7. package/_shared/pr-loop/scripts/terminology_sweep.py +85 -39
  8. package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
  9. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
  12. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
  13. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +94 -4
  14. package/agents/clean-coder.md +3 -0
  15. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  16. package/bin/install.mjs +16 -8
  17. package/bin/install.test.mjs +17 -6
  18. package/commands/CLAUDE.md +0 -1
  19. package/docs/CODE_RULES.md +1 -1
  20. package/hooks/blocking/CLAUDE.md +0 -4
  21. package/hooks/blocking/claude_md_orphan_file_blocker.py +1 -1
  22. package/hooks/blocking/code_rules_constants_config.py +5 -0
  23. package/hooks/blocking/code_rules_docstrings.py +133 -19
  24. package/hooks/blocking/code_rules_enforcer.py +4 -0
  25. package/hooks/blocking/code_rules_imports_logging.py +10 -6
  26. package/hooks/blocking/code_rules_magic_values.py +5 -0
  27. package/hooks/blocking/code_rules_naming_collection.py +15 -8
  28. package/hooks/blocking/code_rules_paired_test.py +3 -2
  29. package/hooks/blocking/code_rules_shared.py +34 -0
  30. package/hooks/blocking/code_rules_string_magic.py +1 -1
  31. package/hooks/blocking/code_rules_unused_imports.py +2 -2
  32. package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
  33. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +29 -0
  34. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
  35. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
  36. package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +24 -0
  37. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
  38. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
  39. package/hooks/blocking/test_code_rules_magic_values.py +54 -0
  40. package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
  41. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
  42. package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
  43. package/hooks/hooks_constants/CLAUDE.md +0 -3
  44. package/hooks/hooks_constants/blocking_check_limits.py +4 -0
  45. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  46. package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
  47. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
  48. package/hooks/validation/post_tool_use_dispatcher.py +1 -1
  49. package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
  50. package/hooks/workflow/CLAUDE.md +2 -8
  51. package/package.json +1 -1
  52. package/rules/CLAUDE.md +1 -1
  53. package/rules/docstring-prose-matches-implementation.md +1 -1
  54. package/rules/env-var-table-code-drift.md +1 -1
  55. package/rules/package-inventory-stale-entry.md +1 -1
  56. package/rules/paired-test-coverage.md +1 -1
  57. package/rules/plain-illustrative-docstrings.md +35 -1
  58. package/skills/CLAUDE.md +6 -1
  59. package/skills/_shared/pr-loop/CLAUDE.md +1 -0
  60. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  61. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  62. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +45 -0
  63. package/skills/_shared/pr-loop/scripts/test_write_handoff.py +201 -0
  64. package/skills/_shared/pr-loop/scripts/write_handoff.py +309 -0
  65. package/skills/autoconverge/SKILL.md +82 -58
  66. package/skills/autoconverge/reference/closing-report.md +6 -6
  67. package/skills/autoconverge/reference/convergence.md +17 -15
  68. package/skills/autoconverge/reference/gotchas.md +6 -3
  69. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
  70. package/skills/autoconverge/workflow/converge.contract.test.mjs +87 -13
  71. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +1 -1
  72. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
  73. package/skills/autoconverge/workflow/converge.mjs +71 -74
  74. package/skills/autoconverge/workflow/render_report.py +7 -11
  75. package/skills/bugteam/CLAUDE.md +1 -1
  76. package/skills/bugteam/PROMPTS.md +7 -6
  77. package/skills/bugteam/SKILL.md +25 -72
  78. package/skills/bugteam/reference/CLAUDE.md +1 -3
  79. package/skills/bugteam/reference/README.md +1 -1
  80. package/skills/bugteam/reference/audit-and-teammates.md +1 -1
  81. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
  82. package/skills/bugteam/reference/team-setup.md +8 -7
  83. package/skills/bugteam/scripts/CLAUDE.md +0 -6
  84. package/skills/bugteam/scripts/README.md +0 -4
  85. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
  86. package/skills/code/SKILL.md +2 -0
  87. package/skills/copilot-review/CLAUDE.md +1 -1
  88. package/skills/copilot-review/SKILL.md +25 -23
  89. package/skills/findbugs/CLAUDE.md +2 -2
  90. package/skills/findbugs/SKILL.md +22 -83
  91. package/skills/fixbugs/SKILL.md +2 -4
  92. package/skills/log-audit/CLAUDE.md +20 -0
  93. package/skills/log-audit/SKILL.md +68 -0
  94. package/skills/log-audit/reference/CLAUDE.md +9 -0
  95. package/skills/log-audit/reference/charter.md +52 -0
  96. package/skills/log-audit/scripts/CLAUDE.md +27 -0
  97. package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
  98. package/skills/log-audit/scripts/collect_log_window.py +199 -0
  99. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
  100. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
  101. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
  102. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
  103. package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
  104. package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
  105. package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
  106. package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
  107. package/skills/monitor-open-prs/SKILL.md +2 -2
  108. package/skills/post-audit-findings/SKILL.md +84 -0
  109. package/skills/pr-converge/CLAUDE.md +2 -0
  110. package/skills/pr-converge/SKILL.md +99 -60
  111. package/skills/pr-converge/reference/CLAUDE.md +1 -1
  112. package/skills/pr-converge/reference/convergence-gates.md +16 -19
  113. package/skills/pr-converge/reference/examples.md +5 -5
  114. package/skills/pr-converge/reference/fix-protocol.md +16 -43
  115. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
  116. package/skills/pr-converge/reference/per-tick.md +24 -45
  117. package/skills/pr-converge/reference/state-schema.md +27 -0
  118. package/skills/pr-converge/scripts/README.md +3 -5
  119. package/skills/pr-fix-protocol/SKILL.md +70 -0
  120. package/skills/pr-loop-lifecycle/SKILL.md +73 -0
  121. package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
  122. package/skills/pr-scope-resolve/SKILL.md +48 -0
  123. package/skills/qbug/CLAUDE.md +4 -4
  124. package/skills/qbug/SKILL.md +46 -144
  125. package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
  126. package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
  127. package/skills/reviewer-gates/SKILL.md +96 -0
  128. package/skills/session-log/CLAUDE.md +7 -7
  129. package/skills/session-log/SKILL.md +27 -44
  130. package/skills/test_markdown_link_integrity.py +103 -0
  131. package/commands/doc-gist.md +0 -16
  132. package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
  133. package/hooks/blocking/conftest.py +0 -30
  134. package/hooks/blocking/md_path_exemptions.py +0 -224
  135. package/hooks/blocking/md_to_html_blocker.py +0 -155
  136. package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
  137. package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
  138. package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
  139. package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
  140. package/hooks/hooks_constants/html_companion_constants.py +0 -20
  141. package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
  142. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
  143. package/hooks/workflow/doc_gist_auto_publish.py +0 -144
  144. package/hooks/workflow/md_to_html_companion.py +0 -358
  145. package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
  146. package/hooks/workflow/test_md_to_html_companion.py +0 -613
  147. package/skills/bugteam/reference/audit-contract.md +0 -163
  148. package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
  149. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
  150. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
  151. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
  152. package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
  153. package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
  154. package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
  155. package/skills/doc-gist/CLAUDE.md +0 -25
  156. package/skills/doc-gist/SKILL.md +0 -97
  157. package/skills/doc-gist/references/CLAUDE.md +0 -9
  158. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
  159. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
  160. package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
  161. package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
  162. package/skills/doc-gist/references/examples/05-design-system.html +0 -629
  163. package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
  164. package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
  165. package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
  166. package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
  167. package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
  168. package/skills/doc-gist/references/examples/11-status-report.html +0 -528
  169. package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
  170. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
  171. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
  172. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
  173. package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
  174. package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
  175. package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
  176. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
  177. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
  178. package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
  179. package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
  180. package/skills/doc-gist/references/examples/README.md +0 -5
  181. package/skills/doc-gist/scripts/CLAUDE.md +0 -27
  182. package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
  183. package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
  184. package/skills/doc-gist/scripts/gist_upload.py +0 -177
  185. package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
  186. /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>