chore: add Biome and document design decisions from grill session
- Add @biomejs/biome 2.5.3 with 2-space indent, double quotes, trailing commas - Add lint and format scripts to root package.json - Update CONTEXT.md: resolve Structured Output shape, add Document AST, Block, Inline Node terms - Add ADR 0002 (remark as internal Markdown parser) - Add ADR 0003 (core defines own AST types) - Add spec: core-parser (Parser implementation and Document AST) - Add spec: cli-view (kitchen view <file> command)
This commit is contained in:
96
.claude/spec/cli-view.md
Normal file
96
.claude/spec/cli-view.md
Normal file
@@ -0,0 +1,96 @@
|
||||
## Problem Statement
|
||||
|
||||
`@kitchen-md/bin` has an empty CLI entry point.
|
||||
Users have no way to view a Recipe File in the terminal — they must open a text editor or Markdown viewer to read a recipe.
|
||||
The `kitchen` binary exists in the scaffold but accepts no commands and produces no output.
|
||||
|
||||
## Solution
|
||||
|
||||
Implement the `kitchen view <file>` subcommand.
|
||||
It reads a Recipe File from disk, passes the content to `@kitchen-md/core`'s `parse` function, and walks the resulting Document AST through a renderer that produces ANSI-styled terminal output.
|
||||
Markdown structure is styled for readability; KitchenMD annotations are rendered as their raw source text for now.
|
||||
|
||||
## User Stories
|
||||
|
||||
1. As a CLI user, I want to run `kitchen view <file>` and see the recipe rendered in the terminal so that I can read it without opening a text editor.
|
||||
2. As a CLI user, I want headings styled distinctly by level so that I can scan the recipe's structure at a glance.
|
||||
3. As a CLI user, I want bold and italic text preserved in the terminal output so that emphasis in the recipe prose is visible.
|
||||
4. As a CLI user, I want frontmatter shown as raw YAML before the recipe body so that I can see the recipe's metadata.
|
||||
5. As a CLI user, I want wikilinks rendered distinctly from surrounding prose so that cross-references are visually identifiable.
|
||||
6. As a CLI user, I want a clear error message when the file does not exist so that I am not left with a cryptic failure.
|
||||
7. As a CLI user, I want the binary to exit with a non-zero code on error so that shell scripts can detect failure.
|
||||
8. As a CLI user, I want ANSI styling suppressed automatically when output is piped so that downstream tools receive clean text.
|
||||
|
||||
## Implementation Decisions
|
||||
|
||||
- commander is used for argument parsing.
|
||||
The `view` subcommand takes a single required positional argument: the path to a Recipe File.
|
||||
No flags or options beyond the file path are implemented at this stage.
|
||||
|
||||
- The entry point reads the file from disk, passes the content string to `parse` from `@kitchen-md/core`, and passes the returned `DocumentAST` to the renderer.
|
||||
File I/O is confined to the entry point.
|
||||
|
||||
- The renderer is a pure function `render(ast: DocumentAST): string`.
|
||||
It walks the Document AST and returns an ANSI-styled string.
|
||||
It has no filesystem access and no side effects.
|
||||
The separation between the entry point (file I/O + commander) and the renderer (pure AST walk) allows the renderer to be tested without invoking a subprocess.
|
||||
|
||||
- **Rendering rules:**
|
||||
- Frontmatter: serialised back to YAML and printed before the document body, followed by a visual separator.
|
||||
- `HeadingBlock`: bold text; level 1 is the most visually prominent, with levels 2–6 progressively less so.
|
||||
- `ParagraphBlock`: inline-rendered text followed by a blank line.
|
||||
- `ListBlock`: each item on its own line, preceded by a bullet character (unordered) or sequential number (ordered).
|
||||
- `CodeBlock`: literal text content, rendered as-is with no syntax highlighting.
|
||||
- `ThematicBreakBlock`: a horizontal rule character string.
|
||||
- `TextNode`: plain string output.
|
||||
- `EmphasisNode` (italic): chalk italic.
|
||||
- `StrongNode` (bold): chalk bold.
|
||||
- `CodeSpanNode`: a visually distinct style (e.g. chalk dim or inverse).
|
||||
- `LinkNode`: rendered as the inline content only; href is not shown.
|
||||
- `WikilinkNode`: display text (or target if no display text), rendered with an underline or distinct colour.
|
||||
- `TransclusionNode`: rendered as its raw source text (e.g. `![[file#section:1]]`).
|
||||
- `IngredientNode`, `CookwareNode`, `TimerNode`: rendered as their raw annotation source text (e.g. `@flour{200 g}`, `$pan{}`, `~2-3 mins`).
|
||||
Distinct annotation styling is out of scope for this spec.
|
||||
|
||||
- chalk is used for all ANSI styling.
|
||||
chalk auto-detects whether stdout is a TTY and disables colour codes when output is piped, satisfying user story 8 without an explicit flag.
|
||||
|
||||
- **Error handling:**
|
||||
- If the file path argument is missing, commander prints usage to stderr and exits with code 1.
|
||||
- If the file does not exist or cannot be read, the entry point prints a human-readable error message to stderr and exits with code 1.
|
||||
- No other error conditions are handled at this stage; parse errors from `@kitchen-md/core` are not expected (the parser is resilient to any valid Markdown string).
|
||||
|
||||
## Testing Decisions
|
||||
|
||||
- Two seams are tested:
|
||||
|
||||
**Renderer unit tests** — given a `DocumentAST` constructed inline in the test, assert the `render` function's output with ANSI codes stripped.
|
||||
Tests cover every Block type, every Inline Node type, frontmatter output, and the visual separator between frontmatter and body.
|
||||
No subprocess invocation; no filesystem access.
|
||||
|
||||
**Smoke tests** — invoke the `kitchen view` binary as a subprocess, capture stdout, strip ANSI codes, and assert the plain text content.
|
||||
Smoke tests use `fixtures/basic.md` as input and assert that the output contains the expected rendered text for the fixture's headings, paragraphs, frontmatter, and wikilinks.
|
||||
Smoke tests use Bun's subprocess API, consistent with the project scaffold's smoke test approach.
|
||||
|
||||
- ANSI colour and weight choices are visual decisions verified by inspection, not automated test assertions.
|
||||
Assertions operate on stripped plain text only.
|
||||
|
||||
- Tests are co-located with source and follow the naming conventions established in the project scaffold.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Distinct styling for KitchenMD annotation nodes (Ingredient, Cookware, Timer).
|
||||
Annotations render as raw source text in this spec.
|
||||
- Pager support.
|
||||
- Multiple file arguments.
|
||||
- `--no-color` flag (chalk auto-detects TTY).
|
||||
- Syntax highlighting for code blocks.
|
||||
- Formatted frontmatter display (title, servings, tags as a styled header).
|
||||
- Any subcommand other than `view`.
|
||||
|
||||
## Further Notes
|
||||
|
||||
- Annotation raw source text (`@flour{200 g}`, `$pan{}`, `~2-3 mins`) reads naturally as plain text in the terminal.
|
||||
This matches how Recipe Files render in Obsidian without a plugin — the format is designed to be readable without tooling.
|
||||
- The renderer's purity is intentional.
|
||||
Keeping file I/O in the entry point and rendering in a pure function is the minimum seam needed for deterministic unit tests without subprocess overhead.
|
||||
Reference in New Issue
Block a user