Skip to content

Commit f5bc133

Browse files
committed
feat(cli): lead a group with what it is ranked on, then its occurrences
- Put the ranking value and the entry number at the head of each group and draw the occurrences under it as a tree, canonical occurrence marked and first, so the axis the listing is in order on can be followed down the page - Share one set of column widths across a listing, measured over the rows that will actually be written and capped, so a single deep path does not indent every line a reader came for - Add `--decoration <auto|unicode|ascii|none>` to choose the glyph set. Unlike colour it does not follow whether the destination is a terminal: a report written to a file keeps the tree a terminal would have shown, and `auto` falls back to ASCII only on Windows, whose console depends on the active code page - Drop the hard-coded prefix from the run-qualifying notes so the caller says whether the line reads as a note or a warning, and print what unsettles the report before what merely qualifies it - Update both READMEs and the report tests to the listing as it now stands
1 parent 8d1fdca commit f5bc133

13 files changed

Lines changed: 887 additions & 198 deletions

File tree

README.md

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,40 +42,53 @@ left out:
4242
```text
4343
codehelion scan · structural mode · ~/src/codehelion
4444
45-
crates/codehelion-cli/src/scan/structural/reporting.rs:701-722 type-1 ×2 188 tokens priority 0.62 80fecb4e
46-
crates/codehelion-cli/src/scan/structural/reporting.rs:805-826
47-
crates/codehelion-frontend-c/src/ir.rs:435-449 (line_column) type-1 ×2 128 tokens priority 0.59 c8641036
48-
crates/codehelion-frontend-rust/src/ir.rs:377-391 (line_column)
49-
crates/codehelion-core/src/engine/segment.rs:20-35 (brace_pairs) type-1 ×2 115 tokens priority 0.59 cd0956cb
50-
crates/codehelion-frontend-rust/src/units.rs:66-81 (match_braces)
51-
... and 729 more groups (--limit 0 lists every one)
52-
53-
944 groups (type-1 71, type-2 126, type-3 747) · 205 suppressed · sorted by priority
54-
356 files, 134,235 lines, 713,877 tokens · run 1 (replay: codehelion report --run 1)
45+
#1 0.62 type-1 ×2 188 tokens f7f71e71
46+
├─ ◆ crates/codehelion-cli/src/scan/structural/reporting.rs:698-719
47+
└─ crates/codehelion-cli/src/scan/structural/reporting.rs:802-823
48+
49+
#2 0.59 type-1 ×2 128 tokens 814ddea4
50+
├─ ◆ crates/codehelion-frontend-c/src/ir.rs:437-451 line_column
51+
└─ crates/codehelion-frontend-rust/src/ir.rs:379-393 line_column
52+
53+
#3 0.59 type-1 ×2 115 tokens e6b021f2
54+
├─ ◆ crates/codehelion-core/src/engine/segment.rs:20-35 brace_pairs
55+
└─ crates/codehelion-frontend-rust/src/units.rs:66-81 match_braces
56+
57+
... and 760 more groups (--limit 0 lists every one)
58+
59+
968 groups (type-1 71, type-2 126, type-3 771) · 205 suppressed · sorted by priority
60+
361 files, 136,345 lines, 723,964 tokens · run 1 (replay: codehelion report --run 1)
61+
◆ the occurrence a group is measured against
62+
open one: codehelion explain f7f71e71 · list every group: --limit 0
5563
```
5664

57-
The identifier at the end of each heading is the shortest prefix
58-
`codehelion explain` accepts, so a group can be opened straight from the
59-
listing. Anything qualifying the run — a ceiling that fired, a rule that
60-
matched nothing — goes to the error stream instead, which leaves the report on
61-
standard output pipeable:
65+
The ranking value leads each heading because it is what the listing is in
66+
order on. `` marks the occurrence the group is measured against, which is
67+
the one to read first. The identifier that closes the heading is the shortest
68+
prefix `codehelion explain` accepts, so a group can be opened straight from
69+
the listing.
70+
71+
`--decoration ascii` draws the same listing without a character outside
72+
ASCII, and `--decoration none` drops the tree entirely. Anything qualifying
73+
the run — a ceiling that fired, a rule that matched nothing — goes to the
74+
error stream instead, which leaves the report on standard output pipeable:
6275

6376
```text
64-
note: candidate search was truncated by high frequency, high frequency postings; duplication the tree contains may be missing from this report
77+
⚠ warning: candidate search was truncated by high frequency, high frequency postings; duplication the tree contains may be missing from this report
6578
```
6679

6780
`-v` adds what each group was ranked on, including the similarity dimensions
6881
this mode could not measure:
6982

7083
```text
71-
crates/codehelion-cli/src/scan/structural/reporting.rs:701-722 type-1 ×2 188 tokens priority 0.62 80fecb4e
72-
within one file, identifiers 0.95
73-
confidence 0.82, maintenance risk 0.36, refactoring difficulty 0.17 (2 instances, 188-188 tokens, 188 repeated, 1.00 similarity, 1 file(s))
74-
similarity: composite 1.00 (lexical 1.00, structural 1.00, control-flow 1.00, type n/a, api 1.00); cohesion 1.00; confidence high [structural-verify-v1]
75-
content entropy: 5.02 bits
76-
body evidence: loop no, recognised allocation no, at least 15 call site(s)
77-
crates/codehelion-cli/src/scan/structural/reporting.rs:701-722 [canonical] [finding e61a2fda]
78-
crates/codehelion-cli/src/scan/structural/reporting.rs:805-826 [finding 23402ced]
84+
#1 0.62 type-1 ×2 188 tokens f7f71e71
85+
within one file, identifiers 0.95
86+
confidence 0.82, maintenance risk 0.36, refactoring difficulty 0.17 (2 instances, 188-188 tokens, 188 repeated, 1.00 similarity, 1 file(s))
87+
similarity: composite 1.00 (lexical 1.00, structural 1.00, control-flow 1.00, type n/a, api 1.00); cohesion 1.00; confidence high [structural-verify-v1]
88+
content entropy: 5.02 bits
89+
body evidence: loop no, recognised allocation no, at least 15 call site(s)
90+
├─ ◆ crates/codehelion-cli/src/scan/structural/reporting.rs:698-719 [finding 0300f485]
91+
└─ crates/codehelion-cli/src/scan/structural/reporting.rs:802-823 [finding 18957a06]
7992
```
8093

8194
`-vv` adds what the run itself did: the candidate pipeline stage by stage, the
@@ -171,6 +184,7 @@ The main scan controls are:
171184
- `--jobs <n>` sets frontend read-and-lex workers (capped at four times host parallelism); clone grouping and report rendering remain serial. `--no-ignore` also reads ignored files.
172185
- `--baseline <file>` compares with accepted findings; `--show-suppressed`, `--show-siblings`, and `--show-near-misses` expand text output. JSON and SARIF retain those data regardless.
173186
- `-v`/`-vv` choose how much is said about each group, `--limit <n>` how many groups are listed, and `--quiet` prints the groups alone. `--color <auto|always|never>` overrides the terminal detection, and `NO_COLOR` is honoured.
187+
- `--decoration <auto|unicode|ascii|none>` chooses the glyphs the listing is drawn with. Unlike colour it does not follow the destination: a report written to a file keeps the tree a terminal would have shown, because a box-drawing character in a file is still readable where an escape sequence is not. `auto` draws box-drawing characters everywhere except Windows, whose console depends on the active code page.
174188
- `--include-trivial` restores predicate families to their measured priority in Structural and Semantic mode.
175189
- `--fail-on-findings` returns exit code 3 when visible findings remain.
176190
- `--compare-build-variants` and `--compare-languages` request separate Semantic comparisons; they never merge ordinary scan partitions.

README_ja.md

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,45 @@ codehelion 自身のツリーに対する Structural モードの実行結果で
2424
```text
2525
codehelion scan · structural mode · ~/src/codehelion
2626
27-
crates/codehelion-cli/src/scan/structural/reporting.rs:701-722 type-1 ×2 188 tokens priority 0.62 80fecb4e
28-
crates/codehelion-cli/src/scan/structural/reporting.rs:805-826
29-
crates/codehelion-frontend-c/src/ir.rs:435-449 (line_column) type-1 ×2 128 tokens priority 0.59 c8641036
30-
crates/codehelion-frontend-rust/src/ir.rs:377-391 (line_column)
31-
crates/codehelion-core/src/engine/segment.rs:20-35 (brace_pairs) type-1 ×2 115 tokens priority 0.59 cd0956cb
32-
crates/codehelion-frontend-rust/src/units.rs:66-81 (match_braces)
33-
... and 729 more groups (--limit 0 lists every one)
34-
35-
944 groups (type-1 71, type-2 126, type-3 747) · 205 suppressed · sorted by priority
36-
356 files, 134,235 lines, 713,877 tokens · run 1 (replay: codehelion report --run 1)
27+
#1 0.62 type-1 ×2 188 tokens f7f71e71
28+
├─ ◆ crates/codehelion-cli/src/scan/structural/reporting.rs:698-719
29+
└─ crates/codehelion-cli/src/scan/structural/reporting.rs:802-823
30+
31+
#2 0.59 type-1 ×2 128 tokens 814ddea4
32+
├─ ◆ crates/codehelion-frontend-c/src/ir.rs:437-451 line_column
33+
└─ crates/codehelion-frontend-rust/src/ir.rs:379-393 line_column
34+
35+
#3 0.59 type-1 ×2 115 tokens e6b021f2
36+
├─ ◆ crates/codehelion-core/src/engine/segment.rs:20-35 brace_pairs
37+
└─ crates/codehelion-frontend-rust/src/units.rs:66-81 match_braces
38+
39+
... and 760 more groups (--limit 0 lists every one)
40+
41+
968 groups (type-1 71, type-2 126, type-3 771) · 205 suppressed · sorted by priority
42+
361 files, 136,345 lines, 723,964 tokens · run 1 (replay: codehelion report --run 1)
43+
◆ the occurrence a group is measured against
44+
open one: codehelion explain f7f71e71 · list every group: --limit 0
3745
```
3846

39-
各見出しの末尾にある識別子は `codehelion explain` が受け付ける最短の prefix なので、一覧からそのままグループを開けます。上限の発火や何にも一致しなかったルールなど、実行そのものを限定する情報は標準エラー出力に回り、標準出力のレポートはパイプに流せる状態を保ちます。
47+
見出しの先頭に来るのは順位づけの値です。一覧はこの値の順に並んでいるので、並び順をそのまま縦に読めます。`` はそのグループの基準になっている出現箇所、つまり最初に開くべき一件を指します。見出しの末尾にある識別子は `codehelion explain` が受け付ける最短の prefix なので、一覧からそのままグループを開けます。
48+
49+
`--decoration ascii` は同じ一覧を ASCII の範囲だけで描き、`--decoration none` はツリーそのものを落とします。上限の発火や何にも一致しなかったルールなど、実行そのものを限定する情報は標準エラー出力に回り、標準出力のレポートはパイプに流せる状態を保ちます。
4050

4151
```text
42-
note: candidate search was truncated by high frequency, high frequency postings; duplication the tree contains may be missing from this report
52+
⚠ warning: candidate search was truncated by high frequency, high frequency postings; duplication the tree contains may be missing from this report
4353
```
4454

4555
`-v` は各グループの順位づけの根拠を追加します。このモードでは測れなかった similarity の次元も含みます。
4656

4757
```text
48-
crates/codehelion-cli/src/scan/structural/reporting.rs:701-722 type-1 ×2 188 tokens priority 0.62 80fecb4e
49-
within one file, identifiers 0.95
50-
confidence 0.82, maintenance risk 0.36, refactoring difficulty 0.17 (2 instances, 188-188 tokens, 188 repeated, 1.00 similarity, 1 file(s))
51-
similarity: composite 1.00 (lexical 1.00, structural 1.00, control-flow 1.00, type n/a, api 1.00); cohesion 1.00; confidence high [structural-verify-v1]
52-
content entropy: 5.02 bits
53-
body evidence: loop no, recognised allocation no, at least 15 call site(s)
54-
crates/codehelion-cli/src/scan/structural/reporting.rs:701-722 [canonical] [finding e61a2fda]
55-
crates/codehelion-cli/src/scan/structural/reporting.rs:805-826 [finding 23402ced]
58+
#1 0.62 type-1 ×2 188 tokens f7f71e71
59+
within one file, identifiers 0.95
60+
confidence 0.82, maintenance risk 0.36, refactoring difficulty 0.17 (2 instances, 188-188 tokens, 188 repeated, 1.00 similarity, 1 file(s))
61+
similarity: composite 1.00 (lexical 1.00, structural 1.00, control-flow 1.00, type n/a, api 1.00); cohesion 1.00; confidence high [structural-verify-v1]
62+
content entropy: 5.02 bits
63+
body evidence: loop no, recognised allocation no, at least 15 call site(s)
64+
├─ ◆ crates/codehelion-cli/src/scan/structural/reporting.rs:698-719 [finding 0300f485]
65+
└─ crates/codehelion-cli/src/scan/structural/reporting.rs:802-823 [finding 18957a06]
5666
```
5767

5868
`-vv` は実行そのものの記録を追加します。候補パイプラインの段階ごとの通過数、適用された上限、そして完全な識別子です。
@@ -123,6 +133,7 @@ codehelion doctor # 利用可能な解析コンポーネントを表
123133
- `--jobs <n>` は frontend の read/lex worker 数を指定します(host parallelism の 4 倍まで)。clone grouping と report rendering は serial です。`--no-ignore` は無視対象のファイルも読みます。
124134
- `--baseline <file>` は判断済みの finding と比較します。`--show-suppressed``--show-siblings``--show-near-misses` は text 出力を展開します。JSON と SARIF には常にこれらのデータが含まれます。
125135
- `-v` / `-vv` は各グループについて書く量を、`--limit <n>` は列挙するグループ数を決めます。`--quiet` はグループだけを出力します。`--color <auto|always|never>` は端末判定を上書きし、`NO_COLOR` にも従います。
136+
- `--decoration <auto|unicode|ascii|none>` は一覧を描くグリフを選びます。色とは違って出力先には従いません。ファイルに書き出したレポートも端末と同じツリーを保ちます。エスケープシーケンスと違い、罫線素片はファイルの中でも読めるからです。`auto` は Windows を除いて罫線素片を使います。Windows のコンソールはアクティブなコードページ次第で描画が変わるためです。
126137
- `--include-trivial` は Structural / Semantic モードで predicate family を計測済みの priority に戻します。
127138
- `--fail-on-findings` は visible finding が残ると exit code 3 を返します。
128139
- `--compare-build-variants``--compare-languages` は独立した Semantic comparison を要求し、通常の scan partition を混ぜません。

crates/codehelion-cli/src/cli.rs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use clap::{Parser, Subcommand, ValueEnum};
1212
use codehelion_core::discovery::AnalysisMode;
1313
use serde::{Deserialize, Serialize};
1414

15+
use crate::report;
16+
1517
/// Top-level command-line parser.
1618
#[derive(Debug, Parser)]
1719
#[command(name = "codehelion", version, about, long_about = None)]
@@ -159,6 +161,47 @@ impl ColorChoice {
159161
}
160162
}
161163

164+
/// Which glyphs a text report draws its structure with.
165+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
166+
pub enum DecorationChoice {
167+
/// Box-drawing characters for a terminal, ASCII stand-ins elsewhere.
168+
#[default]
169+
Auto,
170+
/// Box-drawing characters and symbols.
171+
Unicode,
172+
/// ASCII stand-ins for every glyph.
173+
Ascii,
174+
/// No tree and no marks, for a report something else reads.
175+
None,
176+
}
177+
178+
impl DecorationChoice {
179+
/// The glyph set this choice draws with.
180+
///
181+
/// Deliberately not conditioned on the destination being a terminal, as
182+
/// colour is. Colour in a file is damage; a box-drawing character in a
183+
/// file is a box-drawing character, and the reader who opens that file
184+
/// wants the same structure the terminal showed. What decides the glyph is
185+
/// whether the console can draw it, which is a platform question: every
186+
/// target this tool builds for reads UTF-8 by default except Windows,
187+
/// whose console still depends on the active code page.
188+
#[must_use]
189+
pub const fn resolve(self) -> report::Decoration {
190+
match self {
191+
Self::Unicode => report::Decoration::Unicode,
192+
Self::Ascii => report::Decoration::Ascii,
193+
Self::None => report::Decoration::None,
194+
Self::Auto => {
195+
if cfg!(windows) {
196+
report::Decoration::Ascii
197+
} else {
198+
report::Decoration::Unicode
199+
}
200+
}
201+
}
202+
}
203+
}
204+
162205
/// How much of a text report to print, shared by every command that renders
163206
/// one.
164207
///
@@ -183,6 +226,9 @@ pub struct ViewArgs {
183226
/// When to colour the report.
184227
#[arg(long, value_enum, default_value_t = ColorChoice::Auto)]
185228
pub color: ColorChoice,
229+
/// Which glyphs the listing draws its structure with.
230+
#[arg(long, value_enum, default_value_t = DecorationChoice::Auto)]
231+
pub decoration: DecorationChoice,
186232
}
187233

188234
/// An axis a report can be put in order on.
@@ -206,12 +252,12 @@ pub enum SortAxis {
206252
impl SortAxis {
207253
/// The report-side axis this selects.
208254
#[must_use]
209-
pub const fn axis(self) -> crate::report::Sort {
255+
pub const fn axis(self) -> report::Sort {
210256
match self {
211-
Self::Priority => crate::report::Sort::Priority,
212-
Self::IdentifierJaccard => crate::report::Sort::IdentifierJaccard,
213-
Self::DuplicatedTokens => crate::report::Sort::DuplicatedTokens,
214-
Self::Instances => crate::report::Sort::Instances,
257+
Self::Priority => report::Sort::Priority,
258+
Self::IdentifierJaccard => report::Sort::IdentifierJaccard,
259+
Self::DuplicatedTokens => report::Sort::DuplicatedTokens,
260+
Self::Instances => report::Sort::Instances,
215261
}
216262
}
217263
}
@@ -231,8 +277,8 @@ impl BaselineMode {
231277
#[must_use]
232278
pub const fn name(self) -> &'static str {
233279
match self {
234-
Self::Suppress => crate::report::BASELINE_SUPPRESS,
235-
Self::Compare => crate::report::BASELINE_COMPARE,
280+
Self::Suppress => report::BASELINE_SUPPRESS,
281+
Self::Compare => report::BASELINE_COMPARE,
236282
}
237283
}
238284
}
@@ -764,6 +810,9 @@ pub struct ExplainArgs {
764810
/// Output format for the detail view.
765811
#[arg(long, value_enum, default_value_t = DetailFormat::Text)]
766812
pub format: DetailFormat,
813+
/// Which glyphs the occurrence list draws its structure with.
814+
#[arg(long, value_enum, default_value_t = DecorationChoice::Auto)]
815+
pub decoration: DecorationChoice,
767816
/// Local database path, overriding the configured location.
768817
#[arg(long)]
769818
pub db: Option<PathBuf>,
@@ -956,6 +1005,25 @@ mod tests {
9561005
assert!(!ColorChoice::Auto.enabled(false));
9571006
}
9581007

1008+
#[test]
1009+
#[allow(clippy::expect_used)] // Parsed glyph state is the test subject.
1010+
fn decoration_is_chosen_apart_from_colour() {
1011+
let parsed = Cli::try_parse_from(["codehelion", "scan"])
1012+
.expect("a scan without a decoration choice");
1013+
let Command::Scan(args) = parsed.command else {
1014+
unreachable!("a scan invocation parses as a scan");
1015+
};
1016+
assert_eq!(args.view.decoration, DecorationChoice::Auto);
1017+
assert_eq!(DecorationChoice::Ascii.resolve(), report::Decoration::Ascii);
1018+
assert_eq!(DecorationChoice::None.resolve(), report::Decoration::None);
1019+
// Unlike colour, the choice does not turn on where the report is
1020+
// going: a file gets the same glyphs the terminal would have shown.
1021+
assert_eq!(
1022+
DecorationChoice::Unicode.resolve(),
1023+
report::Decoration::Unicode
1024+
);
1025+
}
1026+
9591027
#[test]
9601028
#[allow(clippy::expect_used)] // Parsed flag state is the test subject.
9611029
fn include_trivial_keeps_predicate_groups_at_their_measured_priority() {

0 commit comments

Comments
 (0)