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