boost-skill-cli 1.0.20__tar.gz → 1.0.22__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.
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/PKG-INFO +1 -1
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/_version.py +2 -2
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/discovery.py +16 -9
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/quality.py +7 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/output.py +33 -1
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_skill_cli.egg-info/PKG-INFO +1 -1
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/design-roadmap.html +2 -2
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_discovery.py +13 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_quality.py +3 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_mutation_hardening.py +2 -1
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_output.py +89 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/.gitignore +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/CLAUDE.md +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/CONTRIBUTING.md +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/LICENSE +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/MANIFEST.in +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/Makefile +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/README.md +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/SECURITY.md +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/__init__.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/__main__.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/cli.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/__init__.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/bmad.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/configuration.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/hooks.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/info.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/intelligence.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/pkg.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/taps.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/commands/team.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/__init__.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/agents.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/ai.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/catalog.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/claude_settings.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/config.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/dense.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/embed.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/frontmatter.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/gitutil.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/journal.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/lockfile.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/logs.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/paths.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/policy.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/rag.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/registry.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/store.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/core/util.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/data/registries.json +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_cli/errors.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_skill_cli.egg-info/SOURCES.txt +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_skill_cli.egg-info/entry_points.txt +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_skill_cli.egg-info/requires.txt +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_skill_cli.egg-info/top_level.txt +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/.claude/settings.local.json +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/DEBUGGING.md +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/demo.gif +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/demo.tape +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/index.html +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/rag-architecture.md +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/docs/roadmap.html +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/pyproject.toml +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/scripts/build_registries.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/scripts/mutation_gate.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/setup.cfg +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/style/README.md +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/style/boost.css +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/style/boost.js +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/style/demo.html +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/conftest.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_bmad.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_configuration.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_hooks.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_info.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_intelligence.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_pkg.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_taps.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_team.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_everyday_loop.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/make_fixture.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/smoke.sh +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_agents.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_ai.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_catalog.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_claude_settings.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_config.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_dense.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_embed.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_errors_and_cli_table.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_frontmatter.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_gitutil.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_journal.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_lockfile.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_logs.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_paths.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_policy.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_rag.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_registry.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_store.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_util.py +0 -0
- {boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/unit/test_version.py +0 -0
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.22'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 22)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -209,12 +209,16 @@ def cmd_search(argv):
|
|
|
209
209
|
else:
|
|
210
210
|
out.warn(ai.fallback_note())
|
|
211
211
|
shown = scored[:args.limit]
|
|
212
|
-
|
|
212
|
+
# Cap the name column so one long name can't pad every row, and clip each
|
|
213
|
+
# description to the remaining terminal width so results stay one line each.
|
|
214
|
+
name_w = min(max(len(e["name"]) for e, _ in shown), 32)
|
|
215
|
+
cols = out.term_width()
|
|
213
216
|
for e, _s in shown:
|
|
214
|
-
|
|
215
|
-
if e.get("curated")
|
|
216
|
-
|
|
217
|
-
out.
|
|
217
|
+
tag = " " + out.c("★ curated", out.YELLOW) if e.get("curated") else ""
|
|
218
|
+
tag_w = len(" ★ curated") if e.get("curated") else 0
|
|
219
|
+
desc_w = max(cols - 2 - name_w - 2 - tag_w, 8)
|
|
220
|
+
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)
|
|
218
222
|
out.info(out.c("%d match%s · ranked by %s"
|
|
219
223
|
% (len(scored), "" if len(scored) == 1 else "es", ranker),
|
|
220
224
|
out.DIM))
|
|
@@ -450,12 +454,15 @@ def cmd_recommend(argv):
|
|
|
450
454
|
out.info("no recommendations for this stack — try `boost search <keyword>`")
|
|
451
455
|
return 0
|
|
452
456
|
out.info("no stack-specific matches — curated picks instead:")
|
|
453
|
-
width = max(len(r["entry"]["name"]) for r in shown)
|
|
457
|
+
width = min(max(len(r["entry"]["name"]) for r in shown), 32)
|
|
458
|
+
cols = out.term_width()
|
|
454
459
|
for r in shown:
|
|
455
460
|
e = r["entry"]
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
461
|
+
because = "because: %s" % ", ".join(sorted(r["because"]))
|
|
462
|
+
desc_w = max(cols - 2 - width - 2 - (len(because) + 2), 8)
|
|
463
|
+
name_cell = out.c(out.truncate(e["name"], width).ljust(width), out.CYAN)
|
|
464
|
+
out.info(name_cell + " " + out.truncate(e["description"] or "", desc_w)
|
|
465
|
+
+ " " + out.c(because, out.DIM))
|
|
459
466
|
if ai.available():
|
|
460
467
|
picks = _ai_picks(stack, [r["entry"] for r in ranked[:20]])
|
|
461
468
|
if picks:
|
|
@@ -239,6 +239,8 @@ def cmd_doctor(argv):
|
|
|
239
239
|
ap.parse_args(argv)
|
|
240
240
|
issues = 0
|
|
241
241
|
|
|
242
|
+
out.heading("boost doctor")
|
|
243
|
+
|
|
242
244
|
def bad(msg):
|
|
243
245
|
nonlocal issues
|
|
244
246
|
issues += 1
|
|
@@ -341,6 +343,11 @@ def cmd_doctor(argv):
|
|
|
341
343
|
out.ok("lock file integrity OK · log rotation healthy")
|
|
342
344
|
else:
|
|
343
345
|
out.warn("lock file integrity or log rotation needs attention")
|
|
346
|
+
|
|
347
|
+
out.verdict(issues == 0,
|
|
348
|
+
"healthy" if not issues else
|
|
349
|
+
"%d issue%s need attention — see the suggestions above"
|
|
350
|
+
% (issues, _s(issues)))
|
|
344
351
|
return 1 if issues else 0
|
|
345
352
|
|
|
346
353
|
|
|
@@ -10,6 +10,7 @@ Conventions used across all commands:
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
|
+
import shutil
|
|
13
14
|
import sys
|
|
14
15
|
|
|
15
16
|
RESET = "\033[0m"
|
|
@@ -142,7 +143,38 @@ def dim(msg: str) -> None:
|
|
|
142
143
|
|
|
143
144
|
|
|
144
145
|
def heading(msg: str) -> None:
|
|
145
|
-
|
|
146
|
+
# Brand the section marker in Aurora cyan (truecolor, 16-color fallback,
|
|
147
|
+
# plain under NO_COLOR) so every command's headers read as one system.
|
|
148
|
+
print(aurora("==>", "cyan") + " " + c(msg, BOLD))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def verdict(ok: bool, msg: str) -> None:
|
|
152
|
+
"""A dashboard verdict line: a green dot when healthy, amber when not."""
|
|
153
|
+
dot = aurora("●", "green" if ok else "yellow")
|
|
154
|
+
print(" " + dot + " " + c(msg, GREEN if ok else YELLOW))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def term_width(default: int = 80) -> int:
|
|
158
|
+
"""Best-effort terminal column count; a stable default when detached."""
|
|
159
|
+
return shutil.get_terminal_size((default, 20)).columns
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def truncate(text: str, width: int, ellipsis: str = "…") -> str:
|
|
163
|
+
"""Collapse whitespace (including literal \\n / \\t escapes) to single
|
|
164
|
+
spaces, then clip to at most `width` columns with a trailing ellipsis.
|
|
165
|
+
|
|
166
|
+
Keeps list output to one tidy line each — a 2,000-char blob becomes a
|
|
167
|
+
scannable snippet instead of blowing up the pane.
|
|
168
|
+
"""
|
|
169
|
+
text = text.replace("\\n", " ").replace("\\t", " ").replace("\\r", " ")
|
|
170
|
+
text = " ".join(text.split())
|
|
171
|
+
if width <= 0:
|
|
172
|
+
return ""
|
|
173
|
+
if len(text) <= width:
|
|
174
|
+
return text
|
|
175
|
+
if width <= len(ellipsis):
|
|
176
|
+
return ellipsis[:width]
|
|
177
|
+
return text[:width - len(ellipsis)] + ellipsis
|
|
146
178
|
|
|
147
179
|
|
|
148
180
|
def kv(key: str, value: str, width: int = 14) -> None:
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
<div class="rcards">
|
|
279
279
|
|
|
280
280
|
<article class="ritem" data-track="layout" data-impact="high">
|
|
281
|
-
<div class="ritem-top"><span class="rid">BOOST-D05</span><span class="rstatus
|
|
281
|
+
<div class="ritem-top"><span class="rid">BOOST-D05</span><span class="rstatus done">Shipped</span></div>
|
|
282
282
|
<h4>Fix <code>search</code>: truncate & width-clamp results</h4>
|
|
283
283
|
<p>Today <code>boost search</code> is the worst screen in the app: a single result can dump a <b>2,000-character description with literal, unrendered <code>\\n\\n</code> escapes</b>, while names are padded to a fixed 30 columns that waste half the row. Clamp each row to the live terminal width, collapse whitespace/escape artifacts, and ellipsize. This one fix turns a wall of noise into a scannable list.</p>
|
|
284
284
|
<div class="rmeta"><span class="m-cat">layout</span><span>Complexity M</span><span class="m-imp high">Impact High</span><span class="m-wow">★★★★★</span></div>
|
|
@@ -388,7 +388,7 @@
|
|
|
388
388
|
</article>
|
|
389
389
|
|
|
390
390
|
<article class="ritem" data-track="commands" data-impact="med">
|
|
391
|
-
<div class="ritem-top"><span class="rid">BOOST-D15</span><span class="rstatus
|
|
391
|
+
<div class="ritem-top"><span class="rid">BOOST-D15</span><span class="rstatus done">Shipped</span></div>
|
|
392
392
|
<h4><code>doctor</code> as a health dashboard</h4>
|
|
393
393
|
<p><code>doctor</code> is an undifferentiated wall of green checks — no hierarchy, no headline. Reframe it as a panel with a verdict line (<code>● Healthy</code> in green / <code>● Attention</code> in amber), grouped checks, dim sub-detail, and severity color so a problem actually stands out instead of hiding among the ✓s.</p>
|
|
394
394
|
<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>
|
|
@@ -6,6 +6,7 @@ asserting exact output shapes, exit codes, and on-disk cache effects.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
+
import re
|
|
9
10
|
import sqlite3
|
|
10
11
|
import subprocess
|
|
11
12
|
import types
|
|
@@ -104,6 +105,18 @@ class TestSearch:
|
|
|
104
105
|
r = boost("search", "brainstorming")
|
|
105
106
|
assert "★ curated" in r.out
|
|
106
107
|
|
|
108
|
+
def test_result_rows_clamp_to_terminal_width(self, boost, tapped,
|
|
109
|
+
monkeypatch):
|
|
110
|
+
# D05: every result stays one line within the terminal width; a long
|
|
111
|
+
# description is clipped instead of blowing up the pane.
|
|
112
|
+
monkeypatch.setenv("COLUMNS", "60")
|
|
113
|
+
r = boost("search", "workflow")
|
|
114
|
+
ansi = re.compile(r"\x1b\[[0-9;]*m")
|
|
115
|
+
rows = [ln for ln in r.out.splitlines() if ln and "ranked by" not in ln]
|
|
116
|
+
assert rows
|
|
117
|
+
for ln in rows:
|
|
118
|
+
assert len(ansi.sub("", ln)) <= 60
|
|
119
|
+
|
|
107
120
|
def test_smart_without_ai_warns_and_stays_heuristic(self, boost, tapped):
|
|
108
121
|
r = boost("search", "testing", "--smart")
|
|
109
122
|
assert "using the heuristic fallback" in r.out
|
|
@@ -52,12 +52,14 @@ class TestDoctor:
|
|
|
52
52
|
def test_healthy_exact_summary_rc0(self, boost, tapped):
|
|
53
53
|
boost("install", "brainstorming", "commit-messages")
|
|
54
54
|
r = boost("doctor")
|
|
55
|
+
assert "boost doctor" in r.out # branded dashboard header
|
|
55
56
|
assert "git on PATH" in r.out
|
|
56
57
|
assert "1 tap cloned & cached" in r.out
|
|
57
58
|
assert "lock file parses (v3)" in r.out
|
|
58
59
|
assert "2 skills present in store with agent links" in r.out
|
|
59
60
|
assert "2 skills installed · 1 tap synced · 0 broken links" in r.out
|
|
60
61
|
assert "lock file integrity OK · log rotation healthy" in r.out
|
|
62
|
+
assert "● healthy" in r.out # dashboard verdict
|
|
61
63
|
|
|
62
64
|
def test_broken_symlink_rc1(self, boost, installed):
|
|
63
65
|
ghost = paths.home() / ".claude" / "skills" / "ghost"
|
|
@@ -65,6 +67,7 @@ class TestDoctor:
|
|
|
65
67
|
r = boost("doctor", expect=1)
|
|
66
68
|
assert "1 broken symlink in agent dirs — run `boost heal`" in r.out
|
|
67
69
|
assert "1 skill installed · 1 tap synced · 1 broken link" in r.out
|
|
70
|
+
assert "need attention" in r.out # verdict flips on issues
|
|
68
71
|
|
|
69
72
|
def test_missing_store_rc1(self, boost, installed):
|
|
70
73
|
shutil.rmtree(paths.store_dir() / "brainstorming")
|
|
@@ -128,9 +128,10 @@ class TestForcedColorOutput:
|
|
|
128
128
|
"\033[2m hint: try x\033[0m\n")
|
|
129
129
|
|
|
130
130
|
def test_heading_exact_ansi(self, capsys):
|
|
131
|
+
# Aurora cyan marker (truecolor under forced color) + bold title.
|
|
131
132
|
out.heading("Section")
|
|
132
133
|
assert capsys.readouterr().out == (
|
|
133
|
-
"\033[
|
|
134
|
+
"\033[38;2;34;211;238m==>\033[0m \033[1mSection\033[0m\n")
|
|
134
135
|
|
|
135
136
|
def test_c_multi_style_order(self):
|
|
136
137
|
assert out.c("x", out.RED, out.BOLD) == "\033[31m\033[1mx\033[0m"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Unit tests: boost_cli/core/output.py — colors, symbols, tables, confirm."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
|
|
6
7
|
import pytest
|
|
@@ -197,6 +198,86 @@ class TestGradient:
|
|
|
197
198
|
"\033[0m")
|
|
198
199
|
|
|
199
200
|
|
|
201
|
+
class TestHeadingAndVerdictColor:
|
|
202
|
+
def _force_truecolor(self, monkeypatch):
|
|
203
|
+
monkeypatch.setenv("COLORTERM", "truecolor")
|
|
204
|
+
monkeypatch.setenv("CLICOLOR_FORCE", "1")
|
|
205
|
+
|
|
206
|
+
def test_heading_marker_is_cyan_truecolor(self, monkeypatch, capsys):
|
|
207
|
+
self._force_truecolor(monkeypatch)
|
|
208
|
+
output.heading("Section")
|
|
209
|
+
out = capsys.readouterr().out
|
|
210
|
+
assert out.startswith("\033[38;2;34;211;238m==>\033[0m ")
|
|
211
|
+
assert "Section" in out
|
|
212
|
+
|
|
213
|
+
def test_verdict_ok_green_dot_and_green_text(self, monkeypatch, capsys):
|
|
214
|
+
self._force_truecolor(monkeypatch)
|
|
215
|
+
output.verdict(True, "healthy")
|
|
216
|
+
# aurora green dot (#4ade80) + green message text
|
|
217
|
+
assert capsys.readouterr().out == (
|
|
218
|
+
" \033[38;2;74;222;128m●\033[0m \033[32mhealthy\033[0m\n")
|
|
219
|
+
|
|
220
|
+
def test_verdict_bad_yellow_dot_and_yellow_text(self, monkeypatch, capsys):
|
|
221
|
+
self._force_truecolor(monkeypatch)
|
|
222
|
+
output.verdict(False, "1 issue")
|
|
223
|
+
# aurora yellow dot (#facc15) + yellow message text
|
|
224
|
+
assert capsys.readouterr().out == (
|
|
225
|
+
" \033[38;2;250;204;21m●\033[0m \033[33m1 issue\033[0m\n")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestTermWidth:
|
|
229
|
+
def test_honors_columns_env(self, monkeypatch):
|
|
230
|
+
monkeypatch.setenv("COLUMNS", "123")
|
|
231
|
+
assert output.term_width() == 123
|
|
232
|
+
|
|
233
|
+
def test_detected_columns(self, monkeypatch):
|
|
234
|
+
monkeypatch.setattr(output.shutil, "get_terminal_size",
|
|
235
|
+
lambda fb: os.terminal_size((93, 24)))
|
|
236
|
+
assert output.term_width() == 93
|
|
237
|
+
|
|
238
|
+
def test_default_80_when_undetectable(self, monkeypatch):
|
|
239
|
+
monkeypatch.delenv("COLUMNS", raising=False)
|
|
240
|
+
monkeypatch.setattr(output.shutil, "get_terminal_size",
|
|
241
|
+
lambda fb: os.terminal_size(fb))
|
|
242
|
+
assert output.term_width() == 80
|
|
243
|
+
|
|
244
|
+
def test_custom_default_when_undetectable(self, monkeypatch):
|
|
245
|
+
monkeypatch.delenv("COLUMNS", raising=False)
|
|
246
|
+
monkeypatch.setattr(output.shutil, "get_terminal_size",
|
|
247
|
+
lambda fb: os.terminal_size(fb))
|
|
248
|
+
assert output.term_width(77) == 77
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class TestTruncate:
|
|
252
|
+
def test_short_text_unchanged(self):
|
|
253
|
+
assert output.truncate("hello", 80) == "hello"
|
|
254
|
+
|
|
255
|
+
def test_exact_width_not_clipped(self):
|
|
256
|
+
assert output.truncate("abcd", 4) == "abcd"
|
|
257
|
+
|
|
258
|
+
def test_clips_with_ellipsis(self):
|
|
259
|
+
assert output.truncate("abcdef", 4) == "abc…"
|
|
260
|
+
|
|
261
|
+
def test_collapses_real_whitespace(self):
|
|
262
|
+
assert output.truncate("a\n\n b\tc", 80) == "a b c"
|
|
263
|
+
|
|
264
|
+
def test_collapses_literal_escape_sequences(self):
|
|
265
|
+
# descriptions in the wild carry literal backslash-n blobs
|
|
266
|
+
assert output.truncate("a\\n\\nb", 80) == "a b"
|
|
267
|
+
|
|
268
|
+
def test_collapses_literal_tab_escape(self):
|
|
269
|
+
assert output.truncate("a\\tb", 80) == "a b"
|
|
270
|
+
|
|
271
|
+
def test_collapses_literal_cr_escape(self):
|
|
272
|
+
assert output.truncate("a\\rb", 80) == "a b"
|
|
273
|
+
|
|
274
|
+
def test_zero_width_is_empty(self):
|
|
275
|
+
assert output.truncate("abc", 0) == ""
|
|
276
|
+
|
|
277
|
+
def test_width_at_or_below_ellipsis_returns_ellipsis(self):
|
|
278
|
+
assert output.truncate("abcdef", 1) == "…"
|
|
279
|
+
|
|
280
|
+
|
|
200
281
|
class TestHelpers:
|
|
201
282
|
@pytest.fixture(autouse=True)
|
|
202
283
|
def plain(self, monkeypatch):
|
|
@@ -236,6 +317,14 @@ class TestHelpers:
|
|
|
236
317
|
output.heading("Section")
|
|
237
318
|
assert capsys.readouterr().out == "==> Section\n"
|
|
238
319
|
|
|
320
|
+
def test_verdict_healthy_plain(self, capsys):
|
|
321
|
+
output.verdict(True, "healthy")
|
|
322
|
+
assert capsys.readouterr().out == " ● healthy\n"
|
|
323
|
+
|
|
324
|
+
def test_verdict_attention_plain(self, capsys):
|
|
325
|
+
output.verdict(False, "2 issues need attention")
|
|
326
|
+
assert capsys.readouterr().out == " ● 2 issues need attention\n"
|
|
327
|
+
|
|
239
328
|
def test_kv_default_width(self, capsys):
|
|
240
329
|
output.kv("key", "value")
|
|
241
330
|
assert capsys.readouterr().out == " key value\n"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/boost_skill_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boost_skill_cli-1.0.20 → boost_skill_cli-1.0.22}/tests/functional/test_cli_configuration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|