boost-skill-cli 1.0.24__tar.gz → 1.0.26__tar.gz

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 (105) hide show
  1. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/_version.py +2 -2
  3. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/discovery.py +14 -5
  4. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/output.py +13 -0
  5. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  6. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/design-roadmap.html +30 -6
  7. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_discovery.py +16 -0
  8. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_output.py +26 -0
  9. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/.gitignore +0 -0
  10. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/CLAUDE.md +0 -0
  11. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/CONTRIBUTING.md +0 -0
  12. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/LICENSE +0 -0
  13. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/MANIFEST.in +0 -0
  14. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/Makefile +0 -0
  15. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/README.md +0 -0
  16. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/SECURITY.md +0 -0
  17. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost +0 -0
  18. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/__init__.py +0 -0
  19. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/__main__.py +0 -0
  20. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/cli.py +0 -0
  21. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/__init__.py +0 -0
  22. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/bmad.py +0 -0
  23. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/configuration.py +0 -0
  24. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/hooks.py +0 -0
  25. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/info.py +0 -0
  26. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/intelligence.py +0 -0
  27. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/pkg.py +0 -0
  28. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/quality.py +0 -0
  29. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/taps.py +0 -0
  30. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/commands/team.py +0 -0
  31. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/__init__.py +0 -0
  32. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/agents.py +0 -0
  33. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/ai.py +0 -0
  34. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/catalog.py +0 -0
  35. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/claude_settings.py +0 -0
  36. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/config.py +0 -0
  37. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/dense.py +0 -0
  38. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/embed.py +0 -0
  39. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/frontmatter.py +0 -0
  40. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/gitutil.py +0 -0
  41. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/journal.py +0 -0
  42. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/lockfile.py +0 -0
  43. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/logs.py +0 -0
  44. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/paths.py +0 -0
  45. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/policy.py +0 -0
  46. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/rag.py +0 -0
  47. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/registry.py +0 -0
  48. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/store.py +0 -0
  49. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/core/util.py +0 -0
  50. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/data/registries.json +0 -0
  51. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_cli/errors.py +0 -0
  52. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_skill_cli.egg-info/SOURCES.txt +0 -0
  53. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  54. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  55. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_skill_cli.egg-info/requires.txt +0 -0
  56. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/boost_skill_cli.egg-info/top_level.txt +0 -0
  57. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/.claude/settings.local.json +0 -0
  58. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/DEBUGGING.md +0 -0
  59. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/demo.gif +0 -0
  60. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/demo.tape +0 -0
  61. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/index.html +0 -0
  62. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/rag-architecture.md +0 -0
  63. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/docs/roadmap.html +0 -0
  64. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/pyproject.toml +0 -0
  65. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/scripts/build_registries.py +0 -0
  66. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/scripts/mutation_gate.py +0 -0
  67. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/setup.cfg +0 -0
  68. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/style/README.md +0 -0
  69. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/style/boost.css +0 -0
  70. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/style/boost.js +0 -0
  71. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/style/demo.html +0 -0
  72. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/conftest.py +0 -0
  73. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_bmad.py +0 -0
  74. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_configuration.py +0 -0
  75. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_hooks.py +0 -0
  76. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_info.py +0 -0
  77. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_intelligence.py +0 -0
  78. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_pkg.py +0 -0
  79. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_quality.py +0 -0
  80. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_taps.py +0 -0
  81. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_cli_team.py +0 -0
  82. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/functional/test_everyday_loop.py +0 -0
  83. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/make_fixture.py +0 -0
  84. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/smoke.sh +0 -0
  85. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_agents.py +0 -0
  86. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_ai.py +0 -0
  87. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_catalog.py +0 -0
  88. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_claude_settings.py +0 -0
  89. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_config.py +0 -0
  90. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_dense.py +0 -0
  91. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_embed.py +0 -0
  92. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_errors_and_cli_table.py +0 -0
  93. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_frontmatter.py +0 -0
  94. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_gitutil.py +0 -0
  95. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_journal.py +0 -0
  96. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_lockfile.py +0 -0
  97. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_logs.py +0 -0
  98. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_mutation_hardening.py +0 -0
  99. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_paths.py +0 -0
  100. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_policy.py +0 -0
  101. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_rag.py +0 -0
  102. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_registry.py +0 -0
  103. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_store.py +0 -0
  104. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_util.py +0 -0
  105. {boost_skill_cli-1.0.24 → boost_skill_cli-1.0.26}/tests/unit/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.24
3
+ Version: 1.0.26
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.0.24'
22
- __version_tuple__ = version_tuple = (1, 0, 24)
21
+ __version__ = version = '1.0.26'
22
+ __version_tuple__ = version_tuple = (1, 0, 26)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -212,13 +212,19 @@ def cmd_search(argv):
212
212
  # Cap the name column so one long name can't pad every row, and clip each
213
213
  # description to the remaining terminal width so results stay one line each.
214
214
  name_w = min(max(len(e["name"]) for e, _ in shown), 32)
215
+ top = max((s for _e, s in shown), default=0) or 1
215
216
  cols = out.term_width()
216
- for e, _s in shown:
217
+ for e, sc in shown:
218
+ # A relevance meter makes the ranking visible; tint it by magnitude.
219
+ frac = sc / top
220
+ hue = "cyan" if frac >= 0.66 else "violet" if frac >= 0.33 else "pink"
221
+ bar = out.aurora(out.meter(frac), hue)
217
222
  tag = " " + out.c("★ curated", out.YELLOW) if e.get("curated") else ""
218
223
  tag_w = len(" ★ curated") if e.get("curated") else 0
219
- desc_w = max(cols - 2 - name_w - 2 - tag_w, 8)
224
+ desc_w = max(cols - 2 - 5 - name_w - 2 - tag_w, 8) # 5 = meter + space
220
225
  name_cell = out.c(out.truncate(e["name"], name_w).ljust(name_w), out.CYAN)
221
- out.info(name_cell + " " + out.truncate(e["description"] or "", desc_w) + tag)
226
+ out.info(bar + " " + name_cell + " "
227
+ + out.truncate(e["description"] or "", desc_w) + tag)
222
228
  out.info(out.c("%d match%s · ranked by %s"
223
229
  % (len(scored), "" if len(scored) == 1 else "es", ranker),
224
230
  out.DIM))
@@ -602,7 +608,9 @@ def cmd_trending(argv):
602
608
  if not curated:
603
609
  out.info("no curated skills available — add taps with `boost tap --defaults`")
604
610
  return 0
605
- out.table([(e["name"], "v" + e["version"], e["description"])
611
+ descw = max(out.term_width() - 24, 24) # reserve name + version columns
612
+ out.table([(e["name"], "v" + e["version"],
613
+ out.truncate(e["description"], descw))
606
614
  for e in sorted(curated, key=lambda e: e["name"])[:args.limit]])
607
615
  return 0
608
616
  agg = {}
@@ -611,8 +619,9 @@ def cmd_trending(argv):
611
619
  rec = agg.setdefault(name, {"count": 0, "last": ev.get("ts", "")})
612
620
  rec["count"] += 1
613
621
  ranked = sorted(agg.items(), key=lambda kv: (-kv[1]["count"], kv[0]))
622
+ descw = max(out.term_width() - 44, 24) # reserve name/installs/last columns
614
623
  out.table([(name, str(rec["count"]), util.rel_time(rec["last"]),
615
- by_name.get(name, {}).get("description", ""))
624
+ out.truncate(by_name.get(name, {}).get("description", ""), descw))
616
625
  for name, rec in ranked[:args.limit]],
617
626
  headers=("name", "installs", "last", "description"))
618
627
  out.info(out.c("based on local install activity", out.DIM))
@@ -183,6 +183,19 @@ def badge(label: str, hue: str = "cyan") -> str:
183
183
  return aurora("[" + label + "]", hue)
184
184
 
185
185
 
186
+ def meter(fraction: float, width: int = 4) -> str:
187
+ """A tiny proportional bar for a 0..1 fraction — filled ▰ vs empty ▱.
188
+
189
+ Uncolored on purpose so callers can Aurora-tint it by magnitude.
190
+ """
191
+ if fraction < 0:
192
+ fraction = 0.0
193
+ elif fraction > 1:
194
+ fraction = 1.0
195
+ filled = round(fraction * width)
196
+ return "▰" * filled + "▱" * (width - filled)
197
+
198
+
186
199
  def kv(key: str, value: str, width: int = 14) -> None:
187
200
  print(" " + c(key.ljust(width), DIM) + str(value))
188
201
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.24
3
+ Version: 1.0.26
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -142,7 +142,7 @@
142
142
  <code>NO_COLOR</code> and non-TTY are always respected.
143
143
  </p>
144
144
  <div class="stats">
145
- <div class="stat reveal"><b>22</b><span>Design opportunities</span></div>
145
+ <div class="stat reveal"><b>25</b><span>Design opportunities</span></div>
146
146
  <div class="stat reveal"><b>5</b><span>Design tracks</span></div>
147
147
  <div class="stat reveal"><b>0</b><span>New dependencies</span></div>
148
148
  <div class="stat reveal"><b>1:1</b><span>Web ↔ CLI parity goal</span></div>
@@ -213,7 +213,7 @@
213
213
  <button class="pill" data-filter="impact" data-val="med">Med</button>
214
214
  <button class="pill" data-filter="impact" data-val="low">Low</button>
215
215
  </div>
216
- <p class="roadmap-count"><b id="shown">22</b> of <b>22</b> opportunities shown</p>
216
+ <p class="roadmap-count"><b id="shown">25</b> of <b>25</b> opportunities shown</p>
217
217
  </div>
218
218
 
219
219
  <div class="wrap" id="board">
@@ -273,7 +273,7 @@
273
273
  <h3>Structure &amp; Layout</h3>
274
274
  <p>Width-aware output, glass panels, and results that never blow up the pane.</p>
275
275
  </div>
276
- <span class="n">5 items</span>
276
+ <span class="n">7 items</span>
277
277
  </div>
278
278
  <div class="rcards">
279
279
 
@@ -294,7 +294,7 @@
294
294
  </article>
295
295
 
296
296
  <article class="ritem" data-track="layout" data-impact="med">
297
- <div class="ritem-top"><span class="rid">BOOST-D07</span><span class="rstatus proposed">Proposed</span></div>
297
+ <div class="ritem-top"><span class="rid">BOOST-D07</span><span class="rstatus done">Shipped</span></div>
298
298
  <h4>Relevance visualization in search</h4>
299
299
  <p>Results are "ranked by heuristic relevance" but the ranking is invisible — every row looks equal. Render a tiny gradient relevance bar or score glyph per hit and visually separate <b>curated</b> from GitHub-wide matches, so the eye lands on the best result first.</p>
300
300
  <div class="rmeta"><span class="m-cat">layout</span><span>Complexity M</span><span class="m-imp med">Impact Med</span><span class="m-wow">★★★★☆</span></div>
@@ -317,6 +317,22 @@
317
317
  <div class="rref"><code>commands/info.py · preview/cat</code></div>
318
318
  </article>
319
319
 
320
+ <article class="ritem" data-track="layout" data-impact="high">
321
+ <div class="ritem-top"><span class="rid">BOOST-D23</span><span class="rstatus done">Shipped</span></div>
322
+ <h4><code>trending</code> had the same overflow as <code>search</code></h4>
323
+ <p>Grading the un-touched commands surfaced a sibling of D05: <code>boost trending</code> piped raw catalog descriptions straight into <code>table()</code>, so a single 300-char description blew the table across the pane. Fixed by clipping each description through <code>out.truncate()</code> to the width left after the name/installs/last columns — the same helper the search fix introduced.</p>
324
+ <div class="rmeta"><span class="m-cat">layout</span><span>Complexity S</span><span class="m-imp high">Impact High</span><span class="m-wow">★★★★☆</span></div>
325
+ <div class="rref"><code>commands/discovery.py · cmd_trending</code></div>
326
+ </article>
327
+
328
+ <article class="ritem" data-track="layout" data-impact="med">
329
+ <div class="ritem-top"><span class="rid">BOOST-D24</span><span class="rstatus proposed">Proposed</span></div>
330
+ <h4>Width-aware shared <code>table()</code></h4>
331
+ <p><code>out.table()</code> backs 28 call sites, and several still overflow: <code>taps</code> pads the NAME column to the widest repo (<code>K-Dense-AI/claude-scientific-skills</code> = 44 cols) and then prints full URLs, so wide catalogs wrap. The durable fix is to make <code>table()</code> itself width-aware — shrink the widest text column to the terminal and right-align numeric columns — instead of truncating per-caller. Higher blast radius (28 callers, many exact-output tests), so it needs a careful, test-first pass.</p>
332
+ <div class="rmeta"><span class="m-cat">layout</span><span>Complexity L</span><span class="m-imp med">Impact Med</span><span class="m-wow">★★★★☆</span></div>
333
+ <div class="rref"><code>core/output.py:table() · commands/taps.py</code></div>
334
+ </article>
335
+
320
336
  </div>
321
337
  </section>
322
338
 
@@ -375,12 +391,12 @@
375
391
  <h3>Command-by-command Polish</h3>
376
392
  <p>The screens users actually stare at — front door, doctor, list, info.</p>
377
393
  </div>
378
- <span class="n">5 items</span>
394
+ <span class="n">6 items</span>
379
395
  </div>
380
396
  <div class="rcards">
381
397
 
382
398
  <article class="ritem" data-track="commands" data-impact="med">
383
- <div class="ritem-top"><span class="rid">BOOST-D14</span><span class="rstatus progress">In progress</span></div>
399
+ <div class="ritem-top"><span class="rid">BOOST-D14</span><span class="rstatus done">Shipped</span></div>
384
400
  <h4>Front door: gradient wordmark + section glyphs</h4>
385
401
  <p>The <code>boost</code> home screen is the first thing everyone sees. Today the wordmark is plain bold and every group header is the same generic yellow. Paint the <code>boost</code> wordmark with the Aurora gradient and give each command group a colored bullet/glyph keyed to a brand hue — turning a flat list into a branded index.</p>
386
402
  <div class="rmeta"><span class="m-cat">commands</span><span>Complexity M</span><span class="m-imp med">Impact Med</span><span class="m-wow">★★★★★</span></div>
@@ -419,6 +435,14 @@
419
435
  <div class="rref"><code>commands/info.py:117–119 · core/output.py</code></div>
420
436
  </article>
421
437
 
438
+ <article class="ritem" data-track="commands" data-impact="med">
439
+ <div class="ritem-top"><span class="rid">BOOST-D25</span><span class="rstatus proposed">Proposed</span></div>
440
+ <h4>Branded usage &amp; error output</h4>
441
+ <p>Run a subcommand wrong — <code>boost snapshot</code> with no action — and you get raw, unstyled <code>argparse</code>: a multi-line usage dump with a bare <code>error:</code>, nothing like the branded help. Route argparse's usage/error through the Aurora output layer (accent the program name, dim the usage grammar, use the standard <code>Error:</code> affordance) so <em>failure</em> looks as designed as success. Bonus: a "did you mean …" suggestion for unknown commands.</p>
442
+ <div class="rmeta"><span class="m-cat">ux</span><span>Complexity M</span><span class="m-imp med">Impact Med</span><span class="m-wow">★★★☆☆</span></div>
443
+ <div class="rref"><code>cli.py · argparse formatter</code></div>
444
+ </article>
445
+
422
446
  </div>
423
447
  </section>
424
448
 
@@ -69,6 +69,14 @@ class TestSearch:
69
69
  assert r.out.index("commit-messages") < r.out.index("jira-integration")
70
70
  assert "2 matches · ranked by heuristic relevance" in r.out
71
71
 
72
+ def test_results_show_relevance_meter(self, boost, tapped):
73
+ # D07: the ranking is visible as a per-result meter; the top exact
74
+ # match earns a full bar.
75
+ r = boost("search", "commit", "messages")
76
+ assert "▰" in r.out
77
+ top = next(l for l in r.out.splitlines() if "commit-messages" in l)
78
+ assert "▰▰▰▰" in top
79
+
72
80
  def test_limit_caps_rows_but_footer_counts_all(self, boost, tapped):
73
81
  r = boost("search", "workflow", "--limit", "1")
74
82
  assert "tdd-workflow" in r.out
@@ -563,6 +571,14 @@ class TestTrending:
563
571
  assert "jira-integration" in r.out
564
572
  assert "brainstorming" not in r.out
565
573
 
574
+ def test_long_description_is_clipped(self, boost, tapped, monkeypatch):
575
+ # D23: description column must not dump raw — it clips to the width.
576
+ boost("install", "brainstorming")
577
+ monkeypatch.setenv("COLUMNS", "40")
578
+ r = boost("trending")
579
+ assert "brainstorming" in r.out
580
+ assert "…" in r.out
581
+
566
582
 
567
583
  # ---------------------------------------------------------------- stats
568
584
 
@@ -296,6 +296,32 @@ class TestBadge:
296
296
  "\033[38;2;74;222;128m[installed]\033[0m")
297
297
 
298
298
 
299
+ class TestMeter:
300
+ def test_full(self):
301
+ assert output.meter(1.0, 4) == "▰▰▰▰"
302
+
303
+ def test_empty(self):
304
+ assert output.meter(0.0, 4) == "▱▱▱▱"
305
+
306
+ def test_half(self):
307
+ assert output.meter(0.5, 4) == "▰▰▱▱"
308
+
309
+ def test_default_width_is_four(self):
310
+ assert output.meter(1.0) == "▰▰▰▰"
311
+
312
+ def test_clamps_above_one(self):
313
+ # 1.5 lands in (1, 2] — must still clamp, not overflow to 6 cells
314
+ assert output.meter(1.5, 4) == "▰▰▰▰"
315
+ assert output.meter(2.5, 4) == "▰▰▰▰"
316
+
317
+ def test_clamps_below_zero(self):
318
+ assert output.meter(-3.0, 4) == "▱▱▱▱"
319
+
320
+ def test_rounds_to_nearest_cell(self):
321
+ # 0.6 * 5 = 3.0 -> exactly three filled
322
+ assert output.meter(0.6, 5) == "▰▰▰▱▱"
323
+
324
+
299
325
  class TestHelpers:
300
326
  @pytest.fixture(autouse=True)
301
327
  def plain(self, monkeypatch):
File without changes