chore: scaffold Bun workspace monorepo

Sets up the initial project structure: two packages (@kitchen-md/core
and @kitchen-md/bin), shared tsconfig, shared fixtures, per-package
test stubs (unit, integration, smoke), .gitignore, and project
CLAUDE.md with Conventional Commits convention.
This commit is contained in:
2026-07-08 21:33:20 -04:00
commit 220f73d8f7
19 changed files with 312 additions and 0 deletions

12
.claude/CLAUDE.md Normal file
View File

@@ -0,0 +1,12 @@
# kitchen-md
## Commit Messages
Commit messages must follow the Conventional Commits specification.
See [CONVENTIONAL-COMMITS.md](../CONVENTIONAL-COMMITS.md) for the full summary and specification.
Format: `<type>[optional scope]: <description>`
Common types: `feat`, `fix`, `docs`, `chore`, `test`, `refactor`, `build`.
Once files are staged, you may create the commit without waiting for the user to ask.

View File

@@ -0,0 +1,63 @@
## Problem Statement
The `kitchen-md` repository is a fresh git init with no files.
Before any implementation can begin, the project needs a complete structural scaffold: a monorepo layout, two packages, TypeScript configuration, and a testing infrastructure organised by tier.
## Solution
A Bun-workspaces monorepo containing two packages (`@kitchen-md/core` and `@kitchen-md/bin`), a shared TypeScript base configuration, and per-package test scaffolding organised into unit, integration, and smoke tiers.
Synthetic test fixtures are shared at the workspace root so both packages can reference the same canonical recipe files.
## User Stories
1. As a developer, I want a Bun workspace root so that I can manage both packages with a single toolchain and no additional runtime.
2. As a developer, I want `@kitchen-md/core` isolated as its own package so that it can be consumed by a future Obsidian plugin without bundling the CLI.
3. As a developer, I want `@kitchen-md/bin` isolated as its own package so that the CLI binary can be compiled and distributed independently of the library.
4. As a developer, I want a shared TypeScript base configuration at the workspace root so that both packages inherit consistent compiler settings without duplication.
5. As a developer, I want unit test files co-located with their source module so that the test for a module is always findable next to the module itself.
6. As a developer, I want integration test files co-located with the code they exercise so that cross-module test coverage is discoverable alongside the relevant source.
7. As a developer, I want smoke tests in `@kitchen-md/bin` that invoke the CLI directly so that end-to-end user-story coverage is separated from unit-level coverage.
8. As a developer, I want synthetic recipe fixture files at the workspace root so that both packages can reference the same canonical test inputs without duplication.
## Implementation Decisions
- Bun is the single toolchain for the entire project: package manager, runtime, test runner, and native binary compiler (`bun --compile`).
No Node.js runtime is required on target machines.
- The workspace root `package.json` declares Bun workspaces pointing to `packages/core` and `packages/bin`.
- `@kitchen-md/core` lives in `packages/core/` and is structured as a publishable library with a single entry point.
- `@kitchen-md/bin` lives in `packages/bin/`, declares `@kitchen-md/core` as a workspace dependency, and declares a `bin` entry named `kitchen` pointing at the CLI entry point.
- A `tsconfig.json` at the workspace root defines shared compiler settings (strict mode, ESNext target and module, bundler module resolution, Bun types).
Each package's `tsconfig.json` extends the root config.
- A `fixtures/` directory at the workspace root holds synthetic `.md` recipe files.
The primary fixture covers every annotation type (ingredient, cookware, timer) and all format features.
Additional fixture files are acceptable for regression cases or edge cases that cannot fit cleanly into the primary file.
## Testing Decisions
- Tests assert external behaviour only — given an input, assert the output — never implementation internals.
- **Unit tests**: co-located with the source file they test, named `{module}_test.ts`.
All test data is inline raw strings within the test file; no filesystem access.
Both packages have unit tests.
- **Integration tests**: co-located with the source they exercise, naming is flexible.
Integration tests read from the shared `fixtures/` directory.
Both packages have integration tests where they cross file boundaries.
- **Smoke tests**: present only in `@kitchen-md/bin`.
Each smoke test invokes the CLI directly (via subprocess or equivalent) and asserts its output against expected results.
Smoke tests use the shared `fixtures/` directory as input.
Smoke test coverage maps directly to the CLI user stories in the recipe format spec.
- The Bun test runner discovers test files via `*_test.ts` and `*.test.ts` patterns; all test files must match one of these patterns to be picked up automatically.
## Out of Scope
- Parser implementation (covered by the recipe format spec).
- CLI command design and subcommand structure.
- Aisle mapping file format.
- Shopping list generator.
- Publishing either package to a registry.
- CI/CD configuration.
## Further Notes
- The `$` sigil for cookware and the choice of Bun over other runtimes are decisions from the recipe format spec; this scaffold spec assumes them and does not re-litigate them.
- The binary is named `kitchen` (the `bin` key in `@kitchen-md/bin`'s `package.json`), not `kitchen-md` or `kmd`.
- Smoke tests differ from integration tests in degree, not kind: they test the CLI as a black box from the outside rather than testing module interactions from the inside.

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
node_modules/
# compiled CLI binary
packages/bin/kitchen
# local environment overrides
.env
.env.local

57
CONVENTIONAL-COMMITS.md Normal file
View File

@@ -0,0 +1,57 @@
# Conventional Commits
> Sourced from [conventionalcommits.org/en/v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/)
> by the Conventional Commits authors, licensed under
> [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
---
## Summary
The Conventional Commits specification is a lightweight convention on top of commit messages.
It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of.
This convention dovetails with [SemVer](http://semver.org), by describing the features, fixes, and breaking changes made in commit messages.
The commit message should be structured as follows:
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
The commit contains the following structural elements, to communicate intent to the consumers of your library:
1. **fix:** a commit of the _type_ `fix` patches a bug in your codebase (this correlates with `PATCH` in Semantic Versioning).
2. **feat:** a commit of the _type_ `feat` introduces a new feature to the codebase (this correlates with `MINOR` in Semantic Versioning).
3. **BREAKING CHANGE:** a commit that has a footer with a token `BREAKING CHANGE:`, or appends a `!` after the type/scope, introduces a breaking API change (correlating with `MAJOR` in Semantic Versioning). A BREAKING CHANGE can be part of commits of any _type_.
4. _types_ other than `fix:` and `feat:` are allowed, for example `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others.
5. _footers_ other than `BREAKING CHANGE: <description>` may be provided and follow a convention similar to [git trailer format](https://git-scm.com/docs/git-interpret-trailers).
Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE).
A scope may be provided to a commit's type, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays`.
---
## Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).
1. Commits MUST be prefixed with a type, which consists of a noun, `feat`, `fix`, etc., followed by the OPTIONAL scope, OPTIONAL `!`, and REQUIRED terminal colon and space.
2. The type `feat` MUST be used when a commit adds a new feature to your application or library.
3. The type `fix` MUST be used when a commit represents a bug fix for your application.
4. A scope MAY be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., `fix(parser):`.
5. A description MUST immediately follow the colon and space after the type/scope prefix. The description is a short summary of the code changes, e.g., _fix: array parsing issue when multiple spaces were contained in string_.
6. A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description.
7. A commit body is free-form and MAY consist of any number of newline separated paragraphs.
8. One or more footers MAY be provided one blank line after the body. Each footer MUST consist of a word token, followed by either a `:<space>` or `<space>#` separator, followed by a string value (this is inspired by the [git trailer convention](https://git-scm.com/docs/git-interpret-trailers)).
9. A footer's token MUST use `-` in place of whitespace characters, e.g., `Acked-by` (this helps differentiate the footer section from a multi-paragraph body). An exception is made for `BREAKING CHANGE`, which MAY also be used as a token.
10. A footer's value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer token/separator pair is observed.
11. Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the footer section.
12. If included as a footer, a breaking change MUST consist of the uppercase text `BREAKING CHANGE`, followed by a colon, space, and description, e.g., _BREAKING CHANGE: environment variables now take precedence over config files_.
13. If included in the type/scope prefix, breaking changes MUST be indicated by a `!` immediately before the `:`. If `!` is used, `BREAKING CHANGE:` MAY be omitted from the footer section, and the commit description SHALL be used to describe the breaking change.
14. Types other than `feat` and `fix` MAY be used in your commit messages, e.g., _docs: correct spelling of CHANGELOG_.
15. The units of information that make up Conventional Commits MUST NOT be treated as case sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase.
16. BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer.

37
bun.lock Normal file
View File

@@ -0,0 +1,37 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "kitchen-md",
"devDependencies": {
"bun-types": "latest",
},
},
"packages/bin": {
"name": "@kitchen-md/bin",
"version": "0.0.0",
"bin": {
"kitchen": "./src/index.ts",
},
"dependencies": {
"@kitchen-md/core": "workspace:*",
},
},
"packages/core": {
"name": "@kitchen-md/core",
"version": "0.0.0",
},
},
"packages": {
"@kitchen-md/bin": ["@kitchen-md/bin@workspace:packages/bin"],
"@kitchen-md/core": ["@kitchen-md/core@workspace:packages/core"],
"@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
}
}

38
fixtures/basic.md Normal file
View File

@@ -0,0 +1,38 @@
---
title: Classic Pancakes
servings: 4
tags: [breakfast, quick]
source: https://example.com/pancakes
---
# Classic Pancakes
A simple weekend breakfast that comes together in minutes.
Pairs well with [[maple syrup]] or fresh fruit from the [[farmers market]].
## Ingredients
- @flour{200 g}
- @milk{300 ml}
- @eggs{2}
- @unsalted butter{30 g}
- @baking powder{1 tsp}
- @salt{0.5 tsp}
## Method
### Batter
Sift @flour{200 g} and @baking powder{1 tsp} into a $mixing bowl{} along with @salt{0.5 tsp}.
Make a well in the centre and crack in @eggs{2}.
Gradually whisk in @milk{300 ml} until you have a smooth, lump-free batter.
Melt @unsalted butter{30 g} in a $non-stick frying pan{}, then stir most of it into the batter, reserving a little for the pan.
### Cooking
Set the $non-stick frying pan{} over a medium-high flame and let it heat for ~1 min.
Pour in a ladleful of batter using the $ladle{} and swirl to coat the base.
Cook for ~2-3 mins until bubbles appear across the surface, then flip and cook for ~1 min more.
Transfer to a $plate{3} and keep warm in a low oven while you repeat with the remaining batter.
Serve immediately.

12
package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "kitchen-md",
"version": "0.0.0",
"private": true,
"workspaces": ["packages/core", "packages/bin"],
"scripts": {
"test": "bun test"
},
"devDependencies": {
"bun-types": "latest"
}
}

16
packages/bin/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "@kitchen-md/bin",
"version": "0.0.0",
"private": true,
"type": "module",
"bin": {
"kitchen": "./src/index.ts"
},
"scripts": {
"test": "bun test",
"build": "bun build --compile ./src/index.ts --outfile kitchen"
},
"dependencies": {
"@kitchen-md/core": "workspace:*"
}
}

View File

@@ -0,0 +1 @@
// CLI entry point

View File

@@ -0,0 +1,6 @@
import { describe, test } from "bun:test";
describe("cli", () => {
test.todo("exits with non-zero code when no file argument is given");
test.todo("exits with non-zero code when file does not exist");
});

View File

@@ -0,0 +1,5 @@
import { describe, test } from "bun:test";
describe("cli — integration", () => {
test.todo("invokes core parser and produces output for a real fixture file");
});

View File

@@ -0,0 +1,6 @@
import { describe, test } from "bun:test";
describe("smoke", () => {
test.todo("kitchen --help exits with code 0");
test.todo("kitchen parse <fixture> outputs structured JSON covering all annotation types");
});

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

View File

@@ -0,0 +1,12 @@
{
"name": "@kitchen-md/core",
"version": "0.0.0",
"type": "module",
"main": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "bun test"
}
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,15 @@
import { describe, test } from "bun:test";
describe("parser", () => {
test.todo("parses frontmatter fields as-is");
test.todo("extracts ingredient name, quantity, and unit");
test.todo("extracts ingredient without unit");
test.todo("extracts multi-word ingredient name");
test.todo("extracts cookware without quantity");
test.todo("extracts cookware with quantity");
test.todo("extracts multi-word cookware name");
test.todo("extracts timer as single value");
test.todo("extracts timer as range");
test.todo("annotations embedded mid-sentence are captured");
test.todo("standard Markdown elements pass through without interference");
});

View File

@@ -0,0 +1,5 @@
import { describe, test } from "bun:test";
describe("parser — integration", () => {
test.todo("parses the primary fixture and extracts all annotations");
});

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

12
tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"types": ["bun-types"]
}
}