Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
* text=auto eol=lf
quill_simple.html linguist-generated
github_textarea.html linguist-generated

# Use bd merge for beads JSONL files
.beads/issues.jsonl merge=beads
40 changes: 40 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Agent Instructions

This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started.

## Quick Reference

```bash
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --status in_progress # Claim work
bd close <id> # Complete work
bd sync # Sync with git
```

## Landing the Plane (Session Completion)

**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.

**MANDATORY WORKFLOW:**

1. **File issues for remaining work** - Create issues for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **PUSH TO REMOTE** - This is MANDATORY:
```bash
git pull --rebase
bd sync
git push
git status # MUST show "up to date with origin"
```
5. **Clean up** - Clear stashes, prune remote branches
6. **Verify** - All changes committed AND pushed
7. **Hand off** - Provide context for next session

**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds
- NEVER stop before pushing - that leaves work stranded locally
- NEVER say "ready to push when you are" - YOU must push
- If push fails, resolve and retry until it succeeds

49 changes: 7 additions & 42 deletions harper-cli/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use hashbrown::HashMap;
use rayon::prelude::*;

use harper_core::{
linting::{Lint, LintGroup, LintGroupConfig, LintKind},
linting::{Lint, LintGroup, LintGroupConfig, LintKind, rgb_for_lint_kind},
parsers::MarkdownOptions,
spell::{Dictionary, MergedDictionary, MutableDictionary},
{Dialect, DictWordMetadata, Document, Token, TokenKind, remove_overlaps_map},
Expand Down Expand Up @@ -493,7 +493,7 @@ fn single_input_report(

for (rule_name, lints) in named_lints {
for lint in lints {
let (r, g, b) = rgb_for_lint_kind(Some(&lint.lint_kind));
let (r, g, b) = get_rgb_for_lint_kind(Some(&lint.lint_kind));
report_builder = report_builder.with_label(
Label::new((&input_identifier, lint.span.into()))
.with_message(format!(
Expand All @@ -520,7 +520,7 @@ fn single_input_report(
let lk_vec: Vec<(Option<String>, String)> = lint_kinds_vec
.into_iter()
.map(|(lk, c)| {
let (r, g, b) = rgb_for_lint_kind(Some(lk));
let (r, g, b) = get_rgb_for_lint_kind(Some(lk));
(
Some(format!("\x1b[38;2;{r};{g};{b}m")),
format!("[{lk}: {c}]"),
Expand Down Expand Up @@ -595,7 +595,7 @@ fn final_report(
let lint_kind_counts: Vec<(Option<String>, String)> = all_files_lint_kind_counts_vec
.into_iter()
.map(|(lint_kind, c)| {
let (r, g, b) = rgb_for_lint_kind(Some(&lint_kind));
let (r, g, b) = get_rgb_for_lint_kind(Some(&lint_kind));
(
Some(format!("\x1b[38;2;{r};{g};{b}m")),
format!("[{lint_kind}: {c}]"),
Expand Down Expand Up @@ -636,7 +636,7 @@ fn final_report(
let formatted_lint_kind_rule_pairs: Vec<(Option<String>, String)> = lint_kind_rule_pairs
.into_iter()
.map(|ele| {
let (r, g, b) = rgb_for_lint_kind(Some(&ele.0.0));
let (r, g, b) = get_rgb_for_lint_kind(Some(&ele.0.0));
let ansi_prefix = format!("\x1b[38;2;{r};{g};{b}m");
(
Some(ansi_prefix),
Expand Down Expand Up @@ -700,43 +700,8 @@ fn final_report(
}
}

// Note: This must be kept synchronized with:
// packages/lint-framework/src/lint/lintKindColor.ts
// packages/web/src/lib/lintKindColor.ts
// This can be removed when issue #1991 is resolved.
fn lint_kind_to_rgb() -> &'static [(LintKind, (u8, u8, u8))] {
&[
(LintKind::Agreement, (0x22, 0x8B, 0x22)),
(LintKind::BoundaryError, (0x8B, 0x45, 0x13)),
(LintKind::Capitalization, (0x54, 0x0D, 0x6E)),
(LintKind::Eggcorn, (0xFF, 0x8C, 0x00)),
(LintKind::Enhancement, (0x0E, 0xAD, 0x69)),
(LintKind::Formatting, (0x7D, 0x3C, 0x98)),
(LintKind::Grammar, (0x9B, 0x59, 0xB6)),
(LintKind::Malapropism, (0xC7, 0x15, 0x85)),
(LintKind::Miscellaneous, (0x3B, 0xCE, 0xAC)),
(LintKind::Nonstandard, (0x00, 0x8B, 0x8B)),
(LintKind::Punctuation, (0xD4, 0x85, 0x0F)),
(LintKind::Readability, (0x2E, 0x8B, 0x57)),
(LintKind::Redundancy, (0x46, 0x82, 0xB4)),
(LintKind::Regionalism, (0xC0, 0x61, 0xCB)),
(LintKind::Repetition, (0x00, 0xA6, 0x7C)),
(LintKind::Spelling, (0xEE, 0x42, 0x66)),
(LintKind::Style, (0xFF, 0xD2, 0x3F)),
(LintKind::Typo, (0xFF, 0x6B, 0x35)),
(LintKind::Usage, (0x1E, 0x90, 0xFF)),
(LintKind::WordChoice, (0x22, 0x8B, 0x22)),
]
}

fn rgb_for_lint_kind(olk: Option<&LintKind>) -> (u8, u8, u8) {
olk.and_then(|lk| {
lint_kind_to_rgb()
.iter()
.find(|(k, _)| k == lk)
.map(|(_, color)| *color)
})
.unwrap_or((0, 0, 0))
fn get_rgb_for_lint_kind(olk: Option<&LintKind>) -> (u8, u8, u8) {
olk.map(|lk| rgb_for_lint_kind(*lk)).unwrap_or((0, 0, 0))
}

fn print_formatted_items(items: impl IntoIterator<Item = (Option<String>, String)>) {
Expand Down
174 changes: 174 additions & 0 deletions harper-core/src/linting/lint_kind_colors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use super::LintKind;

/// RGB color tuple for a lint kind.
pub struct LintKindColor {
pub kind: LintKind,
pub r: u8,
pub g: u8,
pub b: u8,
pub hex: &'static str,
}

/// Get all lint kind colors in a consistent order.
pub fn lint_kind_colors() -> &'static [LintKindColor] {
&[
LintKindColor {
kind: LintKind::Agreement,
r: 0x22,
g: 0x8B,
b: 0x22,
hex: "#228B22",
},
LintKindColor {
kind: LintKind::BoundaryError,
r: 0x8B,
g: 0x45,
b: 0x13,
hex: "#8B4513",
},
LintKindColor {
kind: LintKind::Capitalization,
r: 0x54,
g: 0x0D,
b: 0x6E,
hex: "#540D6E",
},
LintKindColor {
kind: LintKind::Eggcorn,
r: 0xFF,
g: 0x8C,
b: 0x00,
hex: "#FF8C00",
},
LintKindColor {
kind: LintKind::Enhancement,
r: 0x0E,
g: 0xAD,
b: 0x69,
hex: "#0EAD69",
},
LintKindColor {
kind: LintKind::Formatting,
r: 0x7D,
g: 0x3C,
b: 0x98,
hex: "#7D3C98",
},
LintKindColor {
kind: LintKind::Grammar,
r: 0x9B,
g: 0x59,
b: 0xB6,
hex: "#9B59B6",
},
LintKindColor {
kind: LintKind::Malapropism,
r: 0xC7,
g: 0x15,
b: 0x85,
hex: "#C71585",
},
LintKindColor {
kind: LintKind::Miscellaneous,
r: 0x3B,
g: 0xCE,
b: 0xAC,
hex: "#3BCEAC",
},
LintKindColor {
kind: LintKind::Nonstandard,
r: 0x00,
g: 0x8B,
b: 0x8B,
hex: "#008B8B",
},
LintKindColor {
kind: LintKind::Punctuation,
r: 0xD4,
g: 0x85,
b: 0x0F,
hex: "#D4850F",
},
LintKindColor {
kind: LintKind::Readability,
r: 0x2E,
g: 0x8B,
b: 0x57,
hex: "#2E8B57",
},
LintKindColor {
kind: LintKind::Redundancy,
r: 0x46,
g: 0x82,
b: 0xB4,
hex: "#4682B4",
},
LintKindColor {
kind: LintKind::Regionalism,
r: 0xC0,
g: 0x61,
b: 0xCB,
hex: "#C061CB",
},
LintKindColor {
kind: LintKind::Repetition,
r: 0x00,
g: 0xA6,
b: 0x7C,
hex: "#00A67C",
},
LintKindColor {
kind: LintKind::Spelling,
r: 0xEE,
g: 0x42,
b: 0x66,
hex: "#EE4266",
},
LintKindColor {
kind: LintKind::Style,
r: 0xFF,
g: 0xD2,
b: 0x3F,
hex: "#FFD23F",
},
LintKindColor {
kind: LintKind::Typo,
r: 0xFF,
g: 0x6B,
b: 0x35,
hex: "#FF6B35",
},
LintKindColor {
kind: LintKind::Usage,
r: 0x1E,
g: 0x90,
b: 0xFF,
hex: "#1E90FF",
},
LintKindColor {
kind: LintKind::WordChoice,
r: 0x22,
g: 0x8B,
b: 0x22,
hex: "#228B22",
},
]
}

/// Get the RGB color for a specific lint kind.
pub fn rgb_for_lint_kind(kind: LintKind) -> (u8, u8, u8) {
lint_kind_colors()
.iter()
.find(|c| c.kind == kind)
.map(|c| (c.r, c.g, c.b))
.unwrap_or((0, 0, 0))
}

/// Get the hex color for a specific lint kind.
pub fn hex_for_lint_kind(kind: LintKind) -> &'static str {
lint_kind_colors()
.iter()
.find(|c| c.kind == kind)
.map(|c| c.hex)
.unwrap_or("#000000")
}
Loading
Loading