openhermes 4.3.0 → 4.9.2

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 (96) hide show
  1. package/CONTEXT.md +9 -0
  2. package/README.md +26 -15
  3. package/bootstrap.ts +161 -124
  4. package/harness/agents/oh-browser.md +97 -0
  5. package/harness/agents/oh-builder.md +78 -0
  6. package/harness/agents/oh-facade.md +75 -0
  7. package/harness/agents/oh-fusion.md +45 -0
  8. package/harness/agents/oh-gauntlet.md +71 -0
  9. package/harness/agents/oh-grill.md +71 -0
  10. package/harness/agents/oh-investigate.md +60 -0
  11. package/harness/agents/oh-manifest.md +95 -0
  12. package/harness/agents/oh-plan-review.md +40 -0
  13. package/harness/agents/oh-planner.md +50 -0
  14. package/harness/agents/oh-refactor.md +37 -0
  15. package/harness/agents/oh-retro.md +46 -0
  16. package/harness/agents/oh-review.md +85 -0
  17. package/harness/agents/oh-security.md +83 -0
  18. package/harness/agents/oh-ship.md +76 -0
  19. package/harness/agents/oh-skill-craft.md +38 -0
  20. package/harness/agents/openhermes.md +107 -53
  21. package/harness/codex/AUTOPILOT.md +143 -91
  22. package/harness/codex/CHARTER.md +81 -0
  23. package/harness/commands/oh-doctor.md +193 -14
  24. package/harness/instructions/SHELL.md +76 -0
  25. package/harness/skills/oh-ascii/DEEP.md +292 -0
  26. package/harness/skills/oh-ascii/SKILL.md +31 -0
  27. package/harness/skills/oh-ascii/scripts/check_ascii_alignment.py +596 -0
  28. package/harness/skills/oh-browser/DEEP.md +54 -0
  29. package/harness/skills/oh-browser/SKILL.md +30 -0
  30. package/harness/skills/oh-builder/DEEP.md +63 -0
  31. package/harness/skills/oh-builder/SKILL.md +12 -90
  32. package/harness/skills/oh-expert/DEEP.md +85 -0
  33. package/harness/skills/oh-expert/SKILL.md +13 -106
  34. package/harness/skills/oh-facade/DEEP.md +182 -0
  35. package/harness/skills/oh-facade/SKILL.md +15 -279
  36. package/harness/skills/oh-freeze/DEEP.md +18 -0
  37. package/harness/skills/oh-freeze/SKILL.md +10 -19
  38. package/harness/skills/oh-full-output/DEEP.md +25 -0
  39. package/harness/skills/oh-full-output/SKILL.md +12 -65
  40. package/harness/skills/oh-fusion/DEEP.md +120 -0
  41. package/harness/skills/oh-fusion/SKILL.md +17 -295
  42. package/harness/skills/oh-gauntlet/DEEP.md +77 -0
  43. package/harness/skills/oh-gauntlet/SKILL.md +13 -105
  44. package/harness/skills/oh-grill/DEEP.md +51 -0
  45. package/harness/skills/oh-grill/SKILL.md +12 -63
  46. package/harness/skills/oh-guard/DEEP.md +19 -0
  47. package/harness/skills/oh-guard/SKILL.md +10 -24
  48. package/harness/skills/oh-handoff/DEEP.md +48 -0
  49. package/harness/skills/oh-handoff/SKILL.md +13 -23
  50. package/harness/skills/oh-health/DEEP.md +74 -0
  51. package/harness/skills/oh-health/SKILL.md +13 -76
  52. package/harness/skills/oh-init/DEEP.md +85 -0
  53. package/harness/skills/oh-init/SKILL.md +13 -127
  54. package/harness/skills/oh-investigate/DEEP.md +171 -0
  55. package/harness/skills/oh-investigate/SKILL.md +13 -66
  56. package/harness/skills/oh-issue/DEEP.md +21 -0
  57. package/harness/skills/oh-issue/SKILL.md +11 -27
  58. package/harness/skills/oh-learn/DEEP.md +44 -0
  59. package/harness/skills/oh-learn/SKILL.md +12 -83
  60. package/harness/skills/oh-manifest/DEEP.md +92 -0
  61. package/harness/skills/oh-manifest/SKILL.md +11 -108
  62. package/harness/skills/oh-plan-review/DEEP.md +90 -0
  63. package/harness/skills/oh-plan-review/SKILL.md +13 -115
  64. package/harness/skills/oh-planner/DEEP.md +172 -0
  65. package/harness/skills/oh-planner/SKILL.md +12 -149
  66. package/harness/skills/oh-prd/DEEP.md +45 -0
  67. package/harness/skills/oh-prd/SKILL.md +10 -26
  68. package/harness/skills/oh-refactor/DEEP.md +122 -0
  69. package/harness/skills/oh-refactor/SKILL.md +17 -410
  70. package/harness/skills/oh-retro/DEEP.md +26 -0
  71. package/harness/skills/oh-retro/SKILL.md +12 -24
  72. package/harness/skills/oh-review/DEEP.md +87 -0
  73. package/harness/skills/oh-review/SKILL.md +11 -97
  74. package/harness/skills/oh-security/DEEP.md +83 -0
  75. package/harness/skills/oh-security/SKILL.md +14 -96
  76. package/harness/skills/oh-ship/DEEP.md +141 -0
  77. package/harness/skills/oh-ship/SKILL.md +13 -31
  78. package/harness/skills/oh-skill-craft/DEEP.md +369 -0
  79. package/harness/skills/oh-skill-craft/SKILL.md +17 -178
  80. package/harness/skills/oh-skills-link/DEEP.md +16 -0
  81. package/harness/skills/oh-skills-link/SKILL.md +10 -20
  82. package/harness/skills/oh-skills-list/DEEP.md +20 -0
  83. package/harness/skills/oh-skills-list/SKILL.md +9 -22
  84. package/harness/skills/oh-triage/DEEP.md +23 -0
  85. package/harness/skills/oh-triage/SKILL.md +8 -24
  86. package/harness/skills/oh-worktree/DEEP.md +169 -0
  87. package/harness/skills/oh-worktree/SKILL.md +32 -0
  88. package/lib/harness-resolver.ts +8 -10
  89. package/package.json +5 -3
  90. package/scripts/count-tokens.mjs +158 -0
  91. package/scripts/oh-doctor.ps1 +342 -0
  92. package/harness/codex/CONSTITUTION.md +0 -73
  93. package/harness/codex/ROUTING.md +0 -92
  94. package/harness/instructions/RUNTIME.md +0 -30
  95. package/harness/skills/oh-caveman/SKILL.md +0 -42
  96. package/lib/logger.ts +0 -75
@@ -0,0 +1,596 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # dependencies = []
4
+ # ///
5
+ """
6
+ ASCII Diagram Alignment Validator
7
+
8
+ Validates alignment of box-drawing characters in enclosed box diagrams.
9
+ Skips file tree structures (├── patterns) and handles arrow characters.
10
+ Outputs issues in compiler-like format: file:line:column: severity: message
11
+
12
+ Usage:
13
+ uv run check_ascii_alignment.py <file_or_directory> [--warn-only] [--verbose]
14
+
15
+ Examples:
16
+ uv run check_ascii_alignment.py docs/ARCHITECTURE.md
17
+ uv run check_ascii_alignment.py docs/*.md
18
+ uv run check_ascii_alignment.py docs/ --verbose
19
+ """
20
+ import argparse
21
+ import re
22
+ import sys
23
+ from dataclasses import dataclass
24
+ from enum import Enum
25
+ from pathlib import Path
26
+
27
+ # Box-drawing character sets
28
+ # Light (single) lines
29
+ SINGLE_HORIZONTAL = set('─')
30
+ SINGLE_VERTICAL = set('│')
31
+ # Double lines
32
+ DOUBLE_HORIZONTAL = set('═')
33
+ DOUBLE_VERTICAL = set('║')
34
+ # Heavy (bold) lines - used by graph-easy boxart
35
+ HEAVY_HORIZONTAL = set('━')
36
+ HEAVY_VERTICAL = set('┃')
37
+
38
+ HORIZONTAL = SINGLE_HORIZONTAL | DOUBLE_HORIZONTAL | HEAVY_HORIZONTAL
39
+ VERTICAL = SINGLE_VERTICAL | DOUBLE_VERTICAL | HEAVY_VERTICAL
40
+
41
+ # Corners - Light
42
+ CORNER_TL_LIGHT = set('┌')
43
+ CORNER_TR_LIGHT = set('┐')
44
+ CORNER_BL_LIGHT = set('└')
45
+ CORNER_BR_LIGHT = set('┘')
46
+ # Corners - Double
47
+ CORNER_TL_DOUBLE = set('╔')
48
+ CORNER_TR_DOUBLE = set('╗')
49
+ CORNER_BL_DOUBLE = set('╚')
50
+ CORNER_BR_DOUBLE = set('╝')
51
+ # Corners - Heavy (bold) - used by graph-easy boxart
52
+ CORNER_TL_HEAVY = set('┏')
53
+ CORNER_TR_HEAVY = set('┓')
54
+ CORNER_BL_HEAVY = set('┗')
55
+ CORNER_BR_HEAVY = set('┛')
56
+ # Corners - Rounded (arc) - used by graph-easy shape: rounded
57
+ CORNER_TL_ROUNDED = set('╭')
58
+ CORNER_TR_ROUNDED = set('╮')
59
+ CORNER_BL_ROUNDED = set('╰')
60
+ CORNER_BR_ROUNDED = set('╯')
61
+
62
+ CORNER_TL = CORNER_TL_LIGHT | CORNER_TL_DOUBLE | CORNER_TL_HEAVY | CORNER_TL_ROUNDED
63
+ CORNER_TR = CORNER_TR_LIGHT | CORNER_TR_DOUBLE | CORNER_TR_HEAVY | CORNER_TR_ROUNDED
64
+ CORNER_BL = CORNER_BL_LIGHT | CORNER_BL_DOUBLE | CORNER_BL_HEAVY | CORNER_BL_ROUNDED
65
+ CORNER_BR = CORNER_BR_LIGHT | CORNER_BR_DOUBLE | CORNER_BR_HEAVY | CORNER_BR_ROUNDED
66
+ CORNERS = CORNER_TL | CORNER_TR | CORNER_BL | CORNER_BR
67
+
68
+ # T-junctions - Light
69
+ T_LEFT_LIGHT = set('├')
70
+ T_RIGHT_LIGHT = set('┤')
71
+ T_TOP_LIGHT = set('┬')
72
+ T_BOTTOM_LIGHT = set('┴')
73
+ # T-junctions - Double
74
+ T_LEFT_DOUBLE = set('╠╞╟')
75
+ T_RIGHT_DOUBLE = set('╣╡╢')
76
+ T_TOP_DOUBLE = set('╦╤╥')
77
+ T_BOTTOM_DOUBLE = set('╩╧╨')
78
+ # T-junctions - Heavy (bold) - used by graph-easy boxart
79
+ T_LEFT_HEAVY = set('┣┡┢┝┞┟┠')
80
+ T_RIGHT_HEAVY = set('┫┥┦┧┨┩┪')
81
+ T_TOP_HEAVY = set('┳┭┮┯┰┱┲')
82
+ T_BOTTOM_HEAVY = set('┻┵┶┷┸┹┺')
83
+
84
+ T_LEFT = T_LEFT_LIGHT | T_LEFT_DOUBLE | T_LEFT_HEAVY
85
+ T_RIGHT = T_RIGHT_LIGHT | T_RIGHT_DOUBLE | T_RIGHT_HEAVY
86
+ T_TOP = T_TOP_LIGHT | T_TOP_DOUBLE | T_TOP_HEAVY
87
+ T_BOTTOM = T_BOTTOM_LIGHT | T_BOTTOM_DOUBLE | T_BOTTOM_HEAVY
88
+ T_JUNCTIONS = T_LEFT | T_RIGHT | T_TOP | T_BOTTOM
89
+
90
+ # Crosses - Light, Double, Heavy
91
+ CROSSES = set('┼╬╪╫╋')
92
+
93
+ # Arrow characters (valid terminators for lines)
94
+ # Includes graph-easy arrows: ∨∧ (mathematical symbols used as arrows)
95
+ ARROWS = set('▶▷►▻▸▹→⟶⟹▼▽▾▿↓⇓◀◁◄◅◂◃←⟵⟸▲△▴▵↑⇑∨∧<>')
96
+
97
+ # Block elements - used by graph-easy as decorative borders (valid terminators)
98
+ # ▐ (right half block), ▌ (left half block), ▀ (upper half), ▄ (lower half)
99
+ BLOCK_ELEMENTS = set('▐▌▀▄█░▒▓')
100
+
101
+ # Ellipsis characters - used by graph-easy for truncation (valid terminators)
102
+ # ⋮ (vertical ellipsis), ⋯ (horizontal ellipsis), … (horizontal ellipsis)
103
+ ELLIPSIS_CHARS = set('⋮⋯…')
104
+
105
+ # Valid line terminators (arrows + block elements + ellipsis)
106
+ VALID_TERMINATORS = ARROWS | BLOCK_ELEMENTS | ELLIPSIS_CHARS
107
+
108
+ # All box-drawing characters
109
+ ALL_BOX_CHARS = HORIZONTAL | VERTICAL | CORNERS | T_JUNCTIONS | CROSSES
110
+
111
+ # Characters that connect upward
112
+ CONNECTS_UP = VERTICAL | CORNER_BL | CORNER_BR | T_LEFT | T_RIGHT | T_BOTTOM | CROSSES
113
+ # Characters that connect downward
114
+ CONNECTS_DOWN = VERTICAL | CORNER_TL | CORNER_TR | T_LEFT | T_RIGHT | T_TOP | CROSSES
115
+ # Characters that connect left
116
+ CONNECTS_LEFT = HORIZONTAL | CORNER_TR | CORNER_BR | T_TOP | T_BOTTOM | T_RIGHT | CROSSES
117
+ # Characters that connect right
118
+ CONNECTS_RIGHT = HORIZONTAL | CORNER_TL | CORNER_BL | T_TOP | T_BOTTOM | T_LEFT | CROSSES
119
+
120
+ # File tree patterns to skip (require space after dashes for actual tree patterns)
121
+ FILE_TREE_PATTERN = re.compile(r'[├└]── ')
122
+
123
+
124
+ class Severity(Enum):
125
+ ERROR = "error"
126
+ WARNING = "warning"
127
+ INFO = "info"
128
+
129
+
130
+ @dataclass
131
+ class Issue:
132
+ file: str
133
+ line: int
134
+ column: int
135
+ severity: Severity
136
+ message: str
137
+ suggestion: str | None = None
138
+
139
+ def __str__(self) -> str:
140
+ base = f"{self.file}:{self.line}:{self.column}: {self.severity.value}: {self.message}"
141
+ if self.suggestion:
142
+ base += f"\n → Suggestion: {self.suggestion}"
143
+ return base
144
+
145
+
146
+ def is_file_tree_block(lines: list[str]) -> bool:
147
+ """Detect if a code block is a file tree structure."""
148
+ tree_line_count = 0
149
+ for line in lines:
150
+ if FILE_TREE_PATTERN.search(line):
151
+ tree_line_count += 1
152
+ # If more than 20% of lines have tree patterns, it's a file tree
153
+ return tree_line_count > len(lines) * 0.2
154
+
155
+
156
+ def is_enclosed_box_diagram(lines: list[str]) -> bool:
157
+ """Detect if a code block contains an enclosed box diagram (with corner characters)."""
158
+ has_tl = any(any(c in CORNER_TL for c in line) for line in lines)
159
+ has_tr = any(any(c in CORNER_TR for c in line) for line in lines)
160
+ has_bl = any(any(c in CORNER_BL for c in line) for line in lines)
161
+ has_br = any(any(c in CORNER_BR for c in line) for line in lines)
162
+
163
+ # For a proper enclosed box, we need at least top-left + bottom-right or top-right + bottom-left
164
+ return (has_tl and has_br) or (has_tr and has_bl) or (has_tl and has_tr and has_bl and has_br)
165
+
166
+
167
+ def get_char_at(lines: list[str], row: int, col: int) -> str | None:
168
+ """Get character at position, handling bounds."""
169
+ if row < 0 or row >= len(lines):
170
+ return None
171
+ line = lines[row]
172
+ if col < 0 or col >= len(line):
173
+ return None
174
+ return line[col]
175
+
176
+
177
+ def find_box_chars_in_line(line: str) -> list[tuple[int, str]]:
178
+ """Find all box-drawing characters in a line with their column positions."""
179
+ results = []
180
+ for col, char in enumerate(line):
181
+ if char in ALL_BOX_CHARS:
182
+ results.append((col, char))
183
+ return results
184
+
185
+
186
+ def check_vertical_alignment(
187
+ lines: list[str], row: int, col: int, char: str, file: str
188
+ ) -> list[Issue]:
189
+ """Check vertical alignment for a character that connects up or down."""
190
+ issues = []
191
+
192
+ # Check if character should connect upward
193
+ if char in CONNECTS_UP:
194
+ above = get_char_at(lines, row - 1, col)
195
+ # Valid connections: box chars that connect down, arrows, terminators,
196
+ # or horizontal lines (graph-easy arrow stem pattern: │ below ─)
197
+ if above is not None and above not in CONNECTS_DOWN and above not in ' \t' and above not in VALID_TERMINATORS and above not in HORIZONTAL:
198
+ issues.append(Issue(
199
+ file=file,
200
+ line=row + 1, # 1-indexed
201
+ column=col + 1,
202
+ severity=Severity.ERROR,
203
+ message=f"vertical connector '{char}' at column {col + 1} has no matching character above (found '{above}')",
204
+ suggestion=f"Add '│', '├', '┤', '┬', or '┼' at line {row}, column {col + 1}, or check if '{char}' should be a different character"
205
+ ))
206
+
207
+ # Check if character should connect downward
208
+ if char in CONNECTS_DOWN:
209
+ below = get_char_at(lines, row + 1, col)
210
+ # Valid connections: box chars that connect up, arrows, terminators,
211
+ # or horizontal lines (graph-easy arrow stem pattern: │ above ─)
212
+ if below is not None and below not in CONNECTS_UP and below not in ' \t' and below not in VALID_TERMINATORS and below not in HORIZONTAL:
213
+ issues.append(Issue(
214
+ file=file,
215
+ line=row + 1,
216
+ column=col + 1,
217
+ severity=Severity.ERROR,
218
+ message=f"vertical connector '{char}' at column {col + 1} has no matching character below (found '{below}')",
219
+ suggestion=f"Add '│', '├', '┤', '┴', or '┼' at line {row + 2}, column {col + 1}, or check if '{char}' should be a different character"
220
+ ))
221
+
222
+ return issues
223
+
224
+
225
+ def check_horizontal_alignment(
226
+ lines: list[str], row: int, col: int, char: str, file: str
227
+ ) -> list[Issue]:
228
+ """Check horizontal alignment for a character that connects left or right."""
229
+ issues = []
230
+ line = lines[row]
231
+
232
+ # Check if character should connect left
233
+ if char in CONNECTS_LEFT:
234
+ left = get_char_at(lines, row, col - 1)
235
+ if col > 0 and left is not None and left not in CONNECTS_RIGHT and left not in ' \t' and left not in VALID_TERMINATORS:
236
+ issues.append(Issue(
237
+ file=file,
238
+ line=row + 1,
239
+ column=col + 1,
240
+ severity=Severity.ERROR,
241
+ message=f"horizontal connector '{char}' has no matching character to the left (found '{left}')",
242
+ suggestion=f"Add '─', '┌', '└', '┬', '┴', or '┼' at column {col}"
243
+ ))
244
+
245
+ # Check if character should connect right
246
+ if char in CONNECTS_RIGHT:
247
+ right = get_char_at(lines, row, col + 1)
248
+ if col < len(line) - 1 and right is not None and right not in CONNECTS_LEFT and right not in ' \t' and right not in VALID_TERMINATORS:
249
+ issues.append(Issue(
250
+ file=file,
251
+ line=row + 1,
252
+ column=col + 1,
253
+ severity=Severity.ERROR,
254
+ message=f"horizontal connector '{char}' has no matching character to the right (found '{right}')",
255
+ suggestion=f"Add '─', '┐', '┘', '┬', '┴', or '┼' at column {col + 2}"
256
+ ))
257
+
258
+ return issues
259
+
260
+
261
+ def check_corner_connections(
262
+ lines: list[str], row: int, col: int, char: str, file: str
263
+ ) -> list[Issue]:
264
+ """Validate corner characters have proper connections."""
265
+ issues = []
266
+
267
+ # Top-left corner: should connect right and down
268
+ if char in CORNER_TL:
269
+ right = get_char_at(lines, row, col + 1)
270
+ below = get_char_at(lines, row + 1, col)
271
+
272
+ if right is not None and right not in CONNECTS_LEFT and right not in ' \t\n' and right not in VALID_TERMINATORS:
273
+ issues.append(Issue(
274
+ file=file,
275
+ line=row + 1,
276
+ column=col + 1,
277
+ severity=Severity.ERROR,
278
+ message=f"top-left corner '{char}' not connected to the right",
279
+ suggestion="Add horizontal line '─' or '═' after the corner"
280
+ ))
281
+
282
+ if below is not None and below not in CONNECTS_UP and below not in ' \t\n' and below not in VALID_TERMINATORS:
283
+ issues.append(Issue(
284
+ file=file,
285
+ line=row + 1,
286
+ column=col + 1,
287
+ severity=Severity.ERROR,
288
+ message=f"top-left corner '{char}' not connected below",
289
+ suggestion=f"Add vertical line '│' or '║' at line {row + 2}, column {col + 1}"
290
+ ))
291
+
292
+ # Top-right corner: should connect left and down
293
+ if char in CORNER_TR:
294
+ left = get_char_at(lines, row, col - 1)
295
+ below = get_char_at(lines, row + 1, col)
296
+
297
+ if left is not None and left not in CONNECTS_RIGHT and left not in ' \t\n' and left not in VALID_TERMINATORS:
298
+ issues.append(Issue(
299
+ file=file,
300
+ line=row + 1,
301
+ column=col + 1,
302
+ severity=Severity.ERROR,
303
+ message=f"top-right corner '{char}' not connected to the left",
304
+ suggestion="Add horizontal line '─' or '═' before the corner"
305
+ ))
306
+
307
+ if below is not None and below not in CONNECTS_UP and below not in ' \t\n' and below not in VALID_TERMINATORS:
308
+ issues.append(Issue(
309
+ file=file,
310
+ line=row + 1,
311
+ column=col + 1,
312
+ severity=Severity.ERROR,
313
+ message=f"top-right corner '{char}' not connected below",
314
+ suggestion=f"Add vertical line '│' or '║' at line {row + 2}, column {col + 1}"
315
+ ))
316
+
317
+ # Bottom-left corner: should connect right and up
318
+ if char in CORNER_BL:
319
+ right = get_char_at(lines, row, col + 1)
320
+ above = get_char_at(lines, row - 1, col)
321
+
322
+ if right is not None and right not in CONNECTS_LEFT and right not in ' \t\n' and right not in VALID_TERMINATORS:
323
+ issues.append(Issue(
324
+ file=file,
325
+ line=row + 1,
326
+ column=col + 1,
327
+ severity=Severity.ERROR,
328
+ message=f"bottom-left corner '{char}' not connected to the right",
329
+ suggestion="Add horizontal line '─' or '═' after the corner"
330
+ ))
331
+
332
+ if above is not None and above not in CONNECTS_DOWN and above not in ' \t\n' and above not in VALID_TERMINATORS:
333
+ issues.append(Issue(
334
+ file=file,
335
+ line=row + 1,
336
+ column=col + 1,
337
+ severity=Severity.ERROR,
338
+ message=f"bottom-left corner '{char}' not connected above",
339
+ suggestion=f"Add vertical line '│' or '║' at line {row}, column {col + 1}"
340
+ ))
341
+
342
+ # Bottom-right corner: should connect left and up
343
+ if char in CORNER_BR:
344
+ left = get_char_at(lines, row, col - 1)
345
+ above = get_char_at(lines, row - 1, col)
346
+
347
+ if left is not None and left not in CONNECTS_RIGHT and left not in ' \t\n' and left not in VALID_TERMINATORS:
348
+ issues.append(Issue(
349
+ file=file,
350
+ line=row + 1,
351
+ column=col + 1,
352
+ severity=Severity.ERROR,
353
+ message=f"bottom-right corner '{char}' not connected to the left",
354
+ suggestion="Add horizontal line '─' or '═' before the corner"
355
+ ))
356
+
357
+ if above is not None and above not in CONNECTS_DOWN and above not in ' \t\n' and above not in VALID_TERMINATORS:
358
+ issues.append(Issue(
359
+ file=file,
360
+ line=row + 1,
361
+ column=col + 1,
362
+ severity=Severity.ERROR,
363
+ message=f"bottom-right corner '{char}' not connected above",
364
+ suggestion=f"Add vertical line '│' or '║' at line {row}, column {col + 1}"
365
+ ))
366
+
367
+ return issues
368
+
369
+
370
+ def extract_code_blocks(content: str) -> list[tuple[int, list[str]]]:
371
+ """Extract code blocks from markdown, returning (start_line, lines) tuples."""
372
+ blocks = []
373
+ lines = content.split('\n')
374
+ in_block = False
375
+ block_start = 0
376
+ block_lines = []
377
+
378
+ for i, line in enumerate(lines):
379
+ stripped = line.strip()
380
+ if stripped.startswith('```'):
381
+ if in_block:
382
+ # End of block
383
+ blocks.append((block_start, block_lines))
384
+ block_lines = []
385
+ in_block = False
386
+ else:
387
+ # Start of block
388
+ in_block = True
389
+ block_start = i + 1 # Next line is start of content
390
+ elif in_block:
391
+ block_lines.append(line)
392
+
393
+ return blocks
394
+
395
+
396
+ def validate_block(block_lines: list[str], block_start: int, file_path: str, verbose: bool) -> list[Issue]:
397
+ """Validate a single code block for ASCII diagram alignment issues."""
398
+ issues = []
399
+
400
+ # Check if this block contains any box-drawing characters
401
+ has_box_chars = any(
402
+ any(c in ALL_BOX_CHARS for c in line)
403
+ for line in block_lines
404
+ )
405
+
406
+ if not has_box_chars:
407
+ return issues
408
+
409
+ # Skip file tree structures
410
+ if is_file_tree_block(block_lines):
411
+ if verbose:
412
+ print(f" Skipping file tree structure at line {block_start + 1}")
413
+ return issues
414
+
415
+ # Only validate enclosed box diagrams
416
+ if not is_enclosed_box_diagram(block_lines):
417
+ if verbose:
418
+ print(f" Skipping non-enclosed diagram at line {block_start + 1}")
419
+ return issues
420
+
421
+ if verbose:
422
+ print(f" Checking enclosed box diagram at line {block_start + 1}")
423
+
424
+ # Validate each line in the block
425
+ for rel_row, line in enumerate(block_lines):
426
+ box_chars = find_box_chars_in_line(line)
427
+
428
+ for col, char in box_chars:
429
+ # Check vertical alignment
430
+ vert_issues = check_vertical_alignment(
431
+ block_lines, rel_row, col, char, file_path
432
+ )
433
+ # Adjust line numbers to absolute file positions
434
+ for issue in vert_issues:
435
+ issues.append(Issue(
436
+ file=issue.file,
437
+ line=block_start + issue.line,
438
+ column=issue.column,
439
+ severity=issue.severity,
440
+ message=issue.message,
441
+ suggestion=issue.suggestion
442
+ ))
443
+
444
+ # Check horizontal alignment
445
+ horiz_issues = check_horizontal_alignment(
446
+ block_lines, rel_row, col, char, file_path
447
+ )
448
+ for issue in horiz_issues:
449
+ issues.append(Issue(
450
+ file=issue.file,
451
+ line=block_start + issue.line,
452
+ column=issue.column,
453
+ severity=issue.severity,
454
+ message=issue.message,
455
+ suggestion=issue.suggestion
456
+ ))
457
+
458
+ # Check corner connections
459
+ if char in CORNERS:
460
+ corner_issues = check_corner_connections(
461
+ block_lines, rel_row, col, char, file_path
462
+ )
463
+ for issue in corner_issues:
464
+ issues.append(Issue(
465
+ file=issue.file,
466
+ line=block_start + issue.line,
467
+ column=issue.column,
468
+ severity=issue.severity,
469
+ message=issue.message,
470
+ suggestion=issue.suggestion
471
+ ))
472
+
473
+ return issues
474
+
475
+
476
+ def validate_file(file_path: Path, verbose: bool = False) -> list[Issue]:
477
+ """Validate a single file for ASCII diagram alignment issues."""
478
+ issues = []
479
+
480
+ try:
481
+ content = file_path.read_text(encoding='utf-8')
482
+ except (OSError, UnicodeDecodeError) as e:
483
+ issues.append(Issue(
484
+ file=str(file_path),
485
+ line=0,
486
+ column=0,
487
+ severity=Severity.ERROR,
488
+ message=f"Could not read file: {e}"
489
+ ))
490
+ return issues
491
+
492
+ # Extract code blocks from markdown
493
+ code_blocks = extract_code_blocks(content)
494
+
495
+ if verbose:
496
+ print(f" Found {len(code_blocks)} code block(s) in {file_path}")
497
+
498
+ for block_start, block_lines in code_blocks:
499
+ block_issues = validate_block(block_lines, block_start, str(file_path), verbose)
500
+ issues.extend(block_issues)
501
+
502
+ return issues
503
+
504
+
505
+ def validate_path(path: Path, verbose: bool = False) -> list[Issue]:
506
+ """Validate a file or directory."""
507
+ issues = []
508
+
509
+ if path.is_file():
510
+ if verbose:
511
+ print(f"Validating: {path}")
512
+ issues.extend(validate_file(path, verbose))
513
+ elif path.is_dir():
514
+ # Find all markdown files
515
+ for md_file in sorted(path.rglob('*.md')):
516
+ if verbose:
517
+ print(f"Validating: {md_file}")
518
+ issues.extend(validate_file(md_file, verbose))
519
+ else:
520
+ issues.append(Issue(
521
+ file=str(path),
522
+ line=0,
523
+ column=0,
524
+ severity=Severity.ERROR,
525
+ message=f"Path does not exist: {path}"
526
+ ))
527
+
528
+ return issues
529
+
530
+
531
+ def main():
532
+ parser = argparse.ArgumentParser(
533
+ description='Validate ASCII box-drawing diagram alignment in markdown files'
534
+ )
535
+ parser.add_argument(
536
+ 'paths',
537
+ nargs='+',
538
+ type=Path,
539
+ help='Files or directories to validate'
540
+ )
541
+ parser.add_argument(
542
+ '--warn-only',
543
+ action='store_true',
544
+ help='Exit 0 even if warnings found (still exit 1 on errors)'
545
+ )
546
+ parser.add_argument(
547
+ '--verbose', '-v',
548
+ action='store_true',
549
+ help='Print verbose progress information'
550
+ )
551
+
552
+ args = parser.parse_args()
553
+
554
+ all_issues: list[Issue] = []
555
+
556
+ for path in args.paths:
557
+ all_issues.extend(validate_path(path, args.verbose))
558
+
559
+ # Deduplicate issues (same file:line:column:message)
560
+ seen = set()
561
+ unique_issues = []
562
+ for issue in all_issues:
563
+ key = (issue.file, issue.line, issue.column, issue.message)
564
+ if key not in seen:
565
+ seen.add(key)
566
+ unique_issues.append(issue)
567
+
568
+ # Sort by file, then line, then column
569
+ unique_issues.sort(key=lambda i: (i.file, i.line, i.column))
570
+
571
+ # Print issues
572
+ for issue in unique_issues:
573
+ print(issue)
574
+
575
+ # Summary
576
+ error_count = sum(1 for i in unique_issues if i.severity == Severity.ERROR)
577
+ warning_count = sum(1 for i in unique_issues if i.severity == Severity.WARNING)
578
+ info_count = sum(1 for i in unique_issues if i.severity == Severity.INFO)
579
+
580
+ if unique_issues:
581
+ print(f"\n{'─' * 60}")
582
+ print(f"Summary: {error_count} error(s), {warning_count} warning(s), {info_count} info")
583
+
584
+ # Exit code
585
+ if error_count > 0:
586
+ sys.exit(1)
587
+ elif warning_count > 0 and not args.warn_only:
588
+ sys.exit(2)
589
+ else:
590
+ if not unique_issues:
591
+ print("✓ No alignment issues found")
592
+ sys.exit(0)
593
+
594
+
595
+ if __name__ == '__main__':
596
+ main()
@@ -0,0 +1,54 @@
1
+ # oh-browser — Deep Reference
2
+
3
+ ## When to Use
4
+
5
+ User needs to interact with websites, automate browser tasks, scrape data, or test web applications. Triggers include: "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data", "test this web app", "login to a site", "automate browser", "browser automation", "web scraping", "check slack".
6
+
7
+ ## Prerequisites
8
+
9
+ - agent-browser installed globally: `npm install -g agent-browser && agent-browser install`
10
+ - Chrome/Chromium (auto-downloaded by `agent-browser install`)
11
+ - State files contain session tokens — add to `.gitignore`, never commit
12
+
13
+ ## Common Patterns
14
+
15
+ - **Annotated screenshots**: `agent-browser screenshot --annotate` — overlays numbered labels matching `@eN` refs. Most reliable method for visual QA and multimodal AI workflows.
16
+ - **Batch execution**: `agent-browser batch "open url" "snapshot" "click @e1"` — avoids per-command startup overhead for multi-step workflows.
17
+ - **Session persistence**: `--session-name <name>` — auto-saves/restores cookies and localStorage. Login once, reuse across sessions.
18
+ - **Auth vault**: `agent-browser auth save <name> --url <url> --username <user>` — stores encrypted credentials. LLM never sees passwords.
19
+ - **Diff**: `agent-browser diff snapshot` — compare current vs last snapshot for change detection. `agent-browser diff screenshot --baseline before.png` for visual pixel diff.
20
+ - **Chrome profile reuse**: `--profile Default` — use existing Chrome login state with zero setup.
21
+ - **Tab labeling**: `agent-browser tab new --label docs <url>` — memorable labels, not numeric indices.
22
+ - **Parallel scrape**: Use `batch --json` with piped command arrays for structured multi-page workflows.
23
+
24
+ ## Common Commands Reference
25
+
26
+ | Task | Command |
27
+ |------|---------|
28
+ | Open URL | `agent-browser open <url>` |
29
+ | Get page state | `agent-browser snapshot -i` (interactive only) |
30
+ | Click | `agent-browser click @eN` or `agent-browser click "css-selector"` |
31
+ | Type text | `agent-browser fill @eN "text"` |
32
+ | Screenshot | `agent-browser screenshot --annotate` |
33
+ | Extract text | `agent-browser get text @eN` |
34
+ | Run JS | `agent-browser eval "document.title"` |
35
+ | Wait for element | `agent-browser wait ".selector"` |
36
+ | Scroll | `agent-browser scroll down 200` |
37
+ | Multi-step | `agent-browser batch "cmd1" "cmd2" "cmd3"` |
38
+
39
+ ## Anti-patterns
40
+
41
+ - Forgetting `agent-browser install` first (Chrome won't be available)
42
+ - Not closing browser sessions (daemon processes leak)
43
+ - Using CSS selectors when `@eN` refs from snapshot are faster and more reliable
44
+ - Running individual commands instead of batch for multi-step workflows
45
+ - Passing credentials in prompts instead of using auth vault
46
+ - Committing state files with session tokens to git
47
+
48
+ ## Security
49
+
50
+ - Use `--allowed-domains "example.com"` to restrict navigation to trusted domains
51
+ - Use auth vault instead of passing credentials in LLM prompts
52
+ - Session state files contain tokens — keep in `.gitignore`
53
+ - `--action-policy ./policy.json` gates destructive actions
54
+ - `--content-boundaries` wraps page output in delimiters to distinguish tool output from page content
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: oh-browser
3
+ description: "Browser automation via agent-browser CLI for web interaction and data extraction"
4
+ tier: 3
5
+ route:
6
+ pass: surface
7
+ fail: oh-browser
8
+ blocker: surface
9
+ ---
10
+
11
+ # oh-browser
12
+
13
+ Browser automation via agent-browser CLI. Navigate pages, fill forms, take screenshots, extract data.
14
+
15
+ ## Steps
16
+
17
+ 1. Install agent-browser and Chrome — `npm install -g agent-browser && agent-browser install`
18
+ 2. Launch browser — `agent-browser open <url>` or `agent-browser open` then navigate
19
+ 3. Snapshot page state — `agent-browser snapshot` returns accessibility tree with `@eN` refs
20
+ 4. Interact using `@eN` refs — `agent-browser click/fill/select/hover @eN`
21
+ 5. Extract data — `agent-browser get text/html @eN` or `agent-browser screenshot`
22
+ 6. Close browser — `agent-browser close`
23
+
24
+ ## Routing
25
+
26
+ | Outcome | Route |
27
+ |---------|-------|
28
+ | pass | → surface |
29
+ | fail | → oh-browser |
30
+ | blocker | → surface |