From ab262b84398205f4ab1671da4af0133260733d3b Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 17 Apr 2026 20:44:37 -0400 Subject: [PATCH 01/13] feat(references): add comprehensive authoring and configuration reference docs - Added authoring references for various subjects including markdown patterns, callouts, tables, and layouts. - Introduced dependency references to map features with configuration needs and extension prerequisites. - Included a scaffold for content types and guidance for customization boundaries. - Updated docs reference outline for later structured extractions. --- .agents/skills/zensical-site/SKILL.md | 88 ++++++++ .../references/authoring-reference-index.md | 36 ++++ .../zensical-site/references/content-types.md | 27 +++ .../references/core/front-matter.md | 62 ++++++ .../references/core/markdown-and-links.md | 60 ++++++ .../dependencies/configuration-reference.md | 196 ++++++++++++++++++ .../dependencies/customization-boundaries.md | 47 +++++ .../dependencies/extension-prereqs.md | 39 ++++ .../navigation-runtime-caveats.md | 34 +++ .../references/docs-reference-outline.md | 93 +++++++++ .../references/shared-patterns.md | 40 ++++ .../callouts-and-interactive-elements.md | 77 +++++++ .../subjects/code-and-technical-content.md | 74 +++++++ .../subjects/data-and-visualization.md | 52 +++++ .../inline-formatting-and-microcontent.md | 64 ++++++ .../references/subjects/layout-and-media.md | 59 ++++++ .../references/voice-and-tone.md | 19 ++ .../templates/page-draft-template.md | 23 ++ .claude/skills/zensical-site | 1 + skills-lock.json | 5 + 20 files changed, 1096 insertions(+) create mode 100644 .agents/skills/zensical-site/SKILL.md create mode 100644 .agents/skills/zensical-site/references/authoring-reference-index.md create mode 100644 .agents/skills/zensical-site/references/content-types.md create mode 100644 .agents/skills/zensical-site/references/core/front-matter.md create mode 100644 .agents/skills/zensical-site/references/core/markdown-and-links.md create mode 100644 .agents/skills/zensical-site/references/dependencies/configuration-reference.md create mode 100644 .agents/skills/zensical-site/references/dependencies/customization-boundaries.md create mode 100644 .agents/skills/zensical-site/references/dependencies/extension-prereqs.md create mode 100644 .agents/skills/zensical-site/references/dependencies/navigation-runtime-caveats.md create mode 100644 .agents/skills/zensical-site/references/docs-reference-outline.md create mode 100644 .agents/skills/zensical-site/references/shared-patterns.md create mode 100644 .agents/skills/zensical-site/references/subjects/callouts-and-interactive-elements.md create mode 100644 .agents/skills/zensical-site/references/subjects/code-and-technical-content.md create mode 100644 .agents/skills/zensical-site/references/subjects/data-and-visualization.md create mode 100644 .agents/skills/zensical-site/references/subjects/inline-formatting-and-microcontent.md create mode 100644 .agents/skills/zensical-site/references/subjects/layout-and-media.md create mode 100644 .agents/skills/zensical-site/references/voice-and-tone.md create mode 100644 .agents/skills/zensical-site/templates/page-draft-template.md create mode 120000 .claude/skills/zensical-site diff --git a/.agents/skills/zensical-site/SKILL.md b/.agents/skills/zensical-site/SKILL.md new file mode 100644 index 00000000..49873b8a --- /dev/null +++ b/.agents/skills/zensical-site/SKILL.md @@ -0,0 +1,88 @@ +--- +name: zensical-site +description: Use this skill whenever the user asks for anything related to https://zensical.org/ including researching pages, drafting or editing site copy, planning information architecture, writing Zensical-style markdown, extracting key points from docs, or preparing publish-ready content updates. Trigger even if the user does not explicitly mention "skill" or "zensical-style" but the work is clearly about zensical.org content. +--- + +# Zensical Site Assistant + +Use this skill to create, revise, and organize content for [zensical.org](https://zensical.org/). + +This skill is intentionally lightweight: use compact reference pages for output-critical rules, and +link out to upstream docs for deep detail. + +## When to use this skill + +Use this skill if the user asks to: + +- Draft new pages/posts for zensical.org. +- Rewrite existing copy for clarity, tone, or SEO. +- Build content outlines, page briefs, or navigation structure. +- Summarize or compare pages from zensical.org. +- Convert rough notes into Zensical-style markdown. + +## Core workflow + +1. Clarify the content objective (page type, audience, and intent). +2. Collect source material from zensical.org and any user-provided notes. +3. Open [Authoring Reference Index](references/authoring-reference-index.md) and load only the + subject references needed for this task. +4. Draft using the output template in this skill. +5. Run a quality pass for tone, structure, links, front matter, and factual consistency. +6. Return final markdown plus a short rationale and suggested next edits. + +## Output format + +Always provide: + +1. `Draft` (markdown ready to paste into the site) +2. `Rationale` (2-5 bullets explaining important choices) +3. `Open Questions` (only when missing info blocks quality) + +Use the page template in [Page Draft Template](templates/page-draft-template.md) unless the user +requests another format. + +## Writing guidance + +- Prefer plain language and concrete examples. +- Keep paragraphs short and scannable. +- Use descriptive headings. +- Avoid hype and vague claims. +- Include links with meaningful anchor text. + +## Reference routing + +Start with: + +- [Authoring Reference Index](references/authoring-reference-index.md) + +Then load only what is needed: + +- [Markdown and Links](references/core/markdown-and-links.md) for links, heading structure, and + title behavior. +- [Front Matter](references/core/front-matter.md) for page metadata. +- [Callouts and Interactive Elements](references/subjects/callouts-and-interactive-elements.md) for + admonitions, buttons, tabs, and tooltips. +- [Code and Technical Content](references/subjects/code-and-technical-content.md) for code blocks, + diagrams, and math. +- [Layout and Media](references/subjects/layout-and-media.md) for grids, images, and icons/emojis. +- [Data and Visualization](references/subjects/data-and-visualization.md) for tables and structured + visual content patterns. +- [Inline Formatting and Microcontent](references/subjects/inline-formatting-and-microcontent.md) + for lists, inline formatting, and footnotes. + +If output depends on setup/runtime behavior, feature flags, or config snippets, load: + +- [Configuration Reference](references/dependencies/configuration-reference.md) +- [Extension Prerequisites](references/dependencies/extension-prereqs.md) +- [Navigation and Runtime Caveats](references/dependencies/navigation-runtime-caveats.md) +- [Customization Boundaries](references/dependencies/customization-boundaries.md) + +Use [Shared Patterns](references/shared-patterns.md) for common rules and shared conventions. + +## Sources and verification + +- If browsing is available, reference exact pages used. +- Flag uncertainty instead of guessing. +- Do not invent product features, policies, or URLs. +- If deeper implementation detail is needed, cite the relevant upstream Zensical doc rather than + expanding these references inline. diff --git a/.agents/skills/zensical-site/references/authoring-reference-index.md b/.agents/skills/zensical-site/references/authoring-reference-index.md new file mode 100644 index 00000000..da710f0d --- /dev/null +++ b/.agents/skills/zensical-site/references/authoring-reference-index.md @@ -0,0 +1,36 @@ +# Authoring Reference Index + +Use this page to decide which reference file to load for a task. + +## How to use this index + +1. Identify the user task type. +2. Load the matching core or subject reference. +3. Load dependency references whenever output depends on setup, feature flags, runtime behavior, or + TOML snippets. +4. If the user asks for advanced behavior, link out to upstream docs. + +## Task routing + +| User need | Open this reference first | Also check | +|-------------------------------------------------------|--------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Write or rewrite a standard docs page | [Markdown and Links](core/markdown-and-links.md) | [Shared Patterns](shared-patterns.md), [Front Matter](core/front-matter.md) | +| Set page metadata | [Front Matter](core/front-matter.md) | [Configuration Reference](dependencies/configuration-reference.md), [Navigation and Runtime Caveats](dependencies/navigation-runtime-caveats.md) | +| Explain or draft `zensical.toml` configuration | [Configuration Reference](dependencies/configuration-reference.md) | [Navigation and Runtime Caveats](dependencies/navigation-runtime-caveats.md), [Customization Boundaries](dependencies/customization-boundaries.md) | +| Use admonitions, tabs, buttons, or tooltips | [Callouts and Interactive Elements](subjects/callouts-and-interactive-elements.md) | [Extension Prerequisites](dependencies/extension-prereqs.md), [Configuration Reference](dependencies/configuration-reference.md) | +| Add code snippets, diagrams, or equations | [Code and Technical Content](subjects/code-and-technical-content.md) | [Extension Prerequisites](dependencies/extension-prereqs.md), [Configuration Reference](dependencies/configuration-reference.md), [Navigation and Runtime Caveats](dependencies/navigation-runtime-caveats.md) | +| Build media-rich layouts | [Layout and Media](subjects/layout-and-media.md) | [Customization Boundaries](dependencies/customization-boundaries.md) | +| Create tables and structured data sections | [Data and Visualization](subjects/data-and-visualization.md) | [Extension Prerequisites](dependencies/extension-prereqs.md), [Configuration Reference](dependencies/configuration-reference.md) | +| Improve text-level formatting and list structure | [Inline Formatting and Microcontent](subjects/inline-formatting-and-microcontent.md) | [Shared Patterns](shared-patterns.md) | +| Figure out whether custom CSS/JS/templates are needed | [Customization Boundaries](dependencies/customization-boundaries.md) | [Docs Reference Outline](docs-reference-outline.md) | + +## When to escalate to upstream docs + +Escalate (link out) when the task asks for: + +- complex extension options not covered in these references +- deep theme customization or override internals +- unusual runtime behavior across multiple plugins +- edge-case compatibility behavior that affects production output + +Use upstream links from [Docs Reference Outline](docs-reference-outline.md). diff --git a/.agents/skills/zensical-site/references/content-types.md b/.agents/skills/zensical-site/references/content-types.md new file mode 100644 index 00000000..78f0deb4 --- /dev/null +++ b/.agents/skills/zensical-site/references/content-types.md @@ -0,0 +1,27 @@ +# Content Types (Scaffold) + +Define conventions by content type so outputs stay consistent. + +## Homepage sections + +- Goal: TODO +- Typical structure: TODO +- CTA style: TODO + +## Documentation pages + +- Goal: TODO +- Typical structure: TODO +- Link behavior: TODO + +## Blog or updates + +- Goal: TODO +- Typical structure: TODO +- Tone notes: TODO + +## Case studies (if applicable) + +- Goal: TODO +- Typical structure: TODO +- Evidence style: TODO diff --git a/.agents/skills/zensical-site/references/core/front-matter.md b/.agents/skills/zensical-site/references/core/front-matter.md new file mode 100644 index 00000000..c28aa674 --- /dev/null +++ b/.agents/skills/zensical-site/references/core/front-matter.md @@ -0,0 +1,62 @@ +# Core: Front Matter + +Use this reference for page metadata and title/visibility controls. + +## Use this when + +- creating pages that need metadata +- setting page title behavior explicitly +- controlling discoverability or layout behavior per page + +## Minimum working pattern + +```markdown +--- +title: Example Page +description: One-sentence summary for readers. +--- + +# Example Page + +Page content starts here. +``` + +## Required config / prerequisites + +- Front matter support is expected in docs authoring flow. +- Some keys influence behavior only when related setup features are enabled (search, navigation, + templates). +- Use [Configuration Reference](../dependencies/configuration-reference.md) when status labels, + template overrides, or search behavior need matching `zensical.toml` setup. + +## What to capture in output + +- page identity: title/description +- optional discoverability controls +- optional layout controls when explicitly requested +- metadata consistency with page body headings + +## Working rules + +- Keep front matter minimal; include only fields needed for the task. +- Align metadata with page purpose and heading text. +- If uncertain about advanced keys, call it out and provide a safe default. + +## Common mistakes to avoid + +- stuffing many optional keys without user need +- title mismatch between front matter and body heading +- using fields that depend on unknown site configuration without a caveat + +## Interactions / caveats + +- title precedence is affected by navigation and markdown + heading ([Markdown and Links](markdown-and-links.md)). +- search/layout behavior can depend on setup + configuration ([Navigation and Runtime Caveats](../dependencies/navigation-runtime-caveats.md)). + +## Deeper docs + +- [Front matter](https://zensical.org/docs/authoring/frontmatter/) +- [Search](https://zensical.org/docs/setup/search/) +- [Navigation](https://zensical.org/docs/setup/navigation/) diff --git a/.agents/skills/zensical-site/references/core/markdown-and-links.md b/.agents/skills/zensical-site/references/core/markdown-and-links.md new file mode 100644 index 00000000..9eac040c --- /dev/null +++ b/.agents/skills/zensical-site/references/core/markdown-and-links.md @@ -0,0 +1,60 @@ +# Core: Markdown and Links + +Use this reference for baseline page authoring, linking, and title behavior. + +## Use this when + +- drafting or rewriting standard docs pages +- creating or updating internal links +- deciding how titles should be derived + +## Minimum working pattern + +```markdown +# Page Title + +Intro paragraph with context. + +## Section Heading + +Use relative links like [Front matter](../frontmatter/). +``` + +## Required config / prerequisites + +- No special extension needed for basic markdown. +- Link output behavior depends on URL mode (`use_directory_urls`). + +## Key rules + +- Write links to docs pages using relative paths. +- Do not hardcode output `.html` links for internal docs pages. +- Keep one `#` heading per page body when title is content-driven. +- If navigation/front matter sets title, ensure content heading stays consistent. + +## Page title precedence + +In practice, title resolution follows this order: + +1. navigation-defined title +2. front matter title +3. first-level markdown heading +4. filename fallback + +## Common mistakes to avoid + +- linking to built HTML instead of docs-relative page paths +- mixing absolute and relative links inconsistently +- adding multiple top-level headings in one page +- assuming content `#` always controls title when nav/front matter overrides it + +## Interactions / caveats + +- front matter can override visible title behavior ([Front Matter](front-matter.md)). +- URL mode impacts generated + paths ([Navigation and Runtime Caveats](../dependencies/navigation-runtime-caveats.md)). + +## Deeper docs + +- [Markdown](https://zensical.org/docs/authoring/markdown/) +- [Basics: use_directory_urls](https://zensical.org/docs/setup/basics/#use_directory_urls) diff --git a/.agents/skills/zensical-site/references/dependencies/configuration-reference.md b/.agents/skills/zensical-site/references/dependencies/configuration-reference.md new file mode 100644 index 00000000..7b70230f --- /dev/null +++ b/.agents/skills/zensical-site/references/dependencies/configuration-reference.md @@ -0,0 +1,196 @@ +# Dependencies: Configuration Reference + +Use this page when the task depends on how Zensical is configured, not just on markdown syntax. + +## Default configuration model + +- Prefer `zensical.toml` examples and guidance. +- Zensical can read `mkdocs.yml`, but this skill should default to TOML unless the user is + explicitly migrating or maintaining YAML. +- Most settings live under `[project]`. +- `site_name` is required in the project config. +- Theme flags usually live under `[project.theme]`. +- Markdown features usually live under `[project.markdown_extensions...]`. +- Template and behavior overrides often use `[project.extra]`, `extra_css`, `extra_javascript`, and + `custom_dir`. + +## Core TOML patterns to recognize + +### Base project settings + +```toml +[project] +site_name = "Example Docs" +site_url = "https://docs.example.com" +docs_dir = "docs" +site_dir = "site" +use_directory_urls = true +``` + +### Theme features + +```toml +[project.theme] +features = ["navigation.instant", "search.highlight"] +``` + +### Navigation structure + +```toml +[project] +nav = [ + { Home = "index.md" }, + { Guides = [{ Getting_started = "guides/getting-started.md" }] } +] +``` + +### Markdown extension blocks + +```toml +[project.markdown_extensions.admonition] + +[project.markdown_extensions.pymdownx.details] + +[project.markdown_extensions.pymdownx.superfences] +``` + +### Asset registration + +```toml +[project] +extra_css = ["stylesheets/extra.css"] +extra_javascript = ["javascripts/extra.js"] +``` + +### Structured JavaScript entries + +```toml +[[project.extra_javascript]] +path = "javascripts/extra.mjs" +type = "module" +``` + +### Extra metadata for templates/features + +```toml +[project.extra.status] +new = "Recently added" +deprecated = "Deprecated" +``` + +## Path and scope rules + +- `docs_dir` and `site_dir` are relative to the config file. +- `docs_dir` cannot currently be `.`. +- `extra_css` and `extra_javascript` paths point to files inside the docs directory. +- `custom_dir` is resolved relative to the config file, not relative to `docs_dir`. +- Use `document$.subscribe(...)` in custom JavaScript when behavior must survive instant navigation. + +## Feature-to-config map + +### Navigation and previews + +- `navigation.instant` requires `site_url` to be set. +- Instant previews also require `site_url`. +- Instant preview automation uses `[project.markdown_extensions.zensical.extensions.preview]` with + nested `configurations`. +- `navigation.prune` is incompatible with `navigation.expand`. +- `navigation.indexes` is incompatible with `toc.integrate`. + +### Front matter behaviors + +- Page status badges require `[project.extra.status]` before `status:` in front matter will mean + anything. +- Page templates require `[project.theme] custom_dir = "overrides"` and a template file in that + overrides directory. +- Search exclusion for an entire page works with front matter alone. +- Search exclusion for sections or blocks requires `attr_list`. + +### Admonitions + +- Base admonitions need `[project.markdown_extensions.admonition]`. +- Collapsible admonitions need `[project.markdown_extensions.pymdownx.details]`. +- Nested admonitions or nested rich blocks need + `[project.markdown_extensions.pymdownx.superfences]`. +- Admonition icon changes use `[project.theme.icon.admonition]`. + +### Buttons and attribute-driven styling + +- Buttons require `[project.markdown_extensions.attr_list]`. +- Any advice that uses classes like `.md-button`, `.copy`, `.select`, or `data-search-exclude` + depends on `attr_list` support. + +### Content tabs + +- Content tabs need `[project.markdown_extensions.pymdownx.superfences]` and + `[project.markdown_extensions.pymdownx.tabbed]` with `alternate_style = true`. +- Linked tabs across pages use `[project.theme] features = ["content.tabs.link"]`. +- Better tab anchors can use `[project.markdown_extensions.pymdownx.tabbed.slugify]`. + +### Code blocks + +- Recommended code-block setup uses `pymdownx.highlight`, `pymdownx.inlinehilite`, + `pymdownx.snippets`, and `pymdownx.superfences`. +- Good defaults for advanced code blocks include `anchor_linenums = true`, `line_spans = "__span"`, + and `pygments_lang_class = true`. +- Global copy/select/annotate controls use theme features such as `content.code.copy`, + `content.code.select`, and `content.code.annotate`. +- Code annotations depend on Pygments-based highlighting, not generic JavaScript highlighters. +- Extra annotation selectors use `[project.extra.annotate]`. + +### Diagrams + +- Mermaid diagrams require `pymdownx.superfences` with a `custom_fences` entry for `mermaid`. +- Standard Mermaid support needs no extra JavaScript beyond that fence config. +- Advanced Mermaid customization can add `extra_javascript`, and any runtime code should work with + `document$.subscribe(...)` if needed. + +### Math + +- Math support needs `[project.markdown_extensions.pymdownx.arithmatex] generic = true`. +- MathJax also needs `extra_javascript` entries for the local setup file and the MathJax CDN. +- KaTeX also needs `extra_javascript`, `extra_css`, and a small runtime script. +- Math runtime scripts should subscribe to `document$` so rendering works with instant navigation. + +### Tooltips and glossary patterns + +- Abbreviations need `[project.markdown_extensions.abbr]`. +- Non-link tooltips and block-level tooltip attributes need `attr_list`. +- Shared glossary snippets need `pymdownx.snippets`, often with `auto_append`. +- Enhanced UI tooltips use the theme feature `content.tooltips`. + +### Tables + +- Basic table support uses `[project.markdown_extensions.tables]`. +- Sortable tables are not built-in config flags; they require `extra_javascript` plus a runtime + helper script. + +## Safe authoring rules for this skill + +- When the user asks for config help, show TOML-first examples unless their repo already uses YAML. +- Do not imply that a markdown feature works automatically if the docs say it needs extension or + theme setup. +- When a feature works only with additional CSS, JS, icons, or overrides, separate the content draft + from the configuration snippet. +- When a feature has compatibility caveats, mention them instead of presenting the setup as + universal. + +## Common gotchas + +- forgetting `[project]` or `[project.theme]` scopes in TOML examples +- assuming `site_url` is optional for instant navigation or instant previews +- using `status:` in front matter without defining statuses in `[project.extra.status]` +- recommending `template:` without configuring `custom_dir` +- suggesting section/block search exclusions without `attr_list` +- combining `navigation.prune` with `navigation.expand` +- combining `navigation.indexes` with `toc.integrate` +- placing extra CSS/JS paths as if they were relative to the repo root instead of the docs directory + +## Deeper docs + +- [Basics](https://zensical.org/docs/setup/basics/) +- [Navigation](https://zensical.org/docs/setup/navigation/) +- [Search](https://zensical.org/docs/setup/search/) +- [Python Markdown](https://zensical.org/docs/setup/extensions/python-markdown/) +- [Python Markdown Extensions](https://zensical.org/docs/setup/extensions/python-markdown-extensions/) +- [Customization](https://zensical.org/docs/customization/) diff --git a/.agents/skills/zensical-site/references/dependencies/customization-boundaries.md b/.agents/skills/zensical-site/references/dependencies/customization-boundaries.md new file mode 100644 index 00000000..c0954cc8 --- /dev/null +++ b/.agents/skills/zensical-site/references/dependencies/customization-boundaries.md @@ -0,0 +1,47 @@ +# Dependencies: Customization Boundaries + +Use this page to decide when markdown alone is enough and when customization work is required. + +## Use this when + +- the user asks for behavior that likely needs CSS, JS, templates, or overrides +- visual requirements go beyond built-in authoring features +- theme-level changes are requested + +## What markdown usually handles well + +- page structure and headings +- links, lists, tables, code blocks, basic admonitions +- standard media insertion + +## What often needs customization + +- custom component styling beyond built-in classes +- bespoke interactive behavior +- template-level layout changes +- advanced theme overrides and block extensions + +## AI behavior rules + +- Do not fake custom behavior with invalid markdown syntax. +- Clearly separate content draft from customization recommendations. +- Provide a short "next implementation step" note when customization is required. + +## Escalation template + +When customization is needed, say: + +1. what part is achievable in markdown now +2. what part requires customization +3. which upstream section should be used next + +## Deeper docs + +- [Customization](https://zensical.org/docs/customization/) +- [Customization: additional CSS](https://zensical.org/docs/customization/#additional-css) +- [Customization: additional JavaScript](https://zensical.org/docs/customization/#additional-javascript) +- [Customization: custom templates](https://zensical.org/docs/customization/#custom-templates) +- [Customization: configuring overrides](https://zensical.org/docs/customization/#configuring-overrides) +- [Customization: template overrides](https://zensical.org/docs/customization/#template-overrides) +- [Customization: overriding blocks](https://zensical.org/docs/customization/#overriding-blocks) +- [Customization: extending the theme](https://zensical.org/docs/customization/#extending-the-theme) diff --git a/.agents/skills/zensical-site/references/dependencies/extension-prereqs.md b/.agents/skills/zensical-site/references/dependencies/extension-prereqs.md new file mode 100644 index 00000000..dd25eab1 --- /dev/null +++ b/.agents/skills/zensical-site/references/dependencies/extension-prereqs.md @@ -0,0 +1,39 @@ +# Dependencies: Extension Prerequisites + +Use this page to check feature prerequisites before generating advanced syntax. + +## Use this when + +- a subject feature might depend on markdown extensions +- output behavior differs by installed extension stack +- you need to decide between advanced syntax and safe fallback syntax + +## Feature-to-prerequisite map + +- Admonitions -> markdown extension support for admonition blocks +- Buttons -> style/extension support for button classes +- Content tabs -> extension support for tabbed content syntax +- Advanced code block options -> code extension support +- Diagrams -> Mermaid support +- Math -> math renderer setup (MathJax or KaTeX path) +- Tooltips/abbreviations -> extension support +- Grids/cards/icons extras -> extension and/or theme support + +## Safe default policy + +- If prerequisite status is unknown, use a simpler markdown pattern. +- Add an `Open Questions` note when setup is required for the requested output. +- Use [Configuration Reference](configuration-reference.md) when the user needs actual + `zensical.toml` snippets. +- Do not invent extension names or settings. + +## Verification checklist + +- Is this feature in a dependency-heavy subject area? +- Is there a known extension requirement? +- Can a simpler equivalent communicate the same content? + +## Deeper docs + +- [Python Markdown](https://zensical.org/docs/setup/extensions/python-markdown/) +- [Python Markdown Extensions](https://zensical.org/docs/setup/extensions/python-markdown-extensions/) diff --git a/.agents/skills/zensical-site/references/dependencies/navigation-runtime-caveats.md b/.agents/skills/zensical-site/references/dependencies/navigation-runtime-caveats.md new file mode 100644 index 00000000..032ee4b8 --- /dev/null +++ b/.agents/skills/zensical-site/references/dependencies/navigation-runtime-caveats.md @@ -0,0 +1,34 @@ +# Dependencies: Navigation and Runtime Caveats + +Use this page when rendering behavior may change due to navigation/runtime configuration. + +## Use this when + +- output includes JS-backed features (tabs, diagrams, math, sortable tables) +- behavior differs between first load and in-site navigation +- page metadata interacts with layout/navigation controls + +## Key caveats to remember + +- Instant navigation can change how some client-side features initialize. +- Sidebar and navigation settings can influence layout expectations. +- URL mode changes internal link output behavior. + +## Authoring implications + +- Keep link syntax compatible with docs-relative navigation. +- Avoid relying on fragile runtime-only behavior unless confirmed. +- Mention caveats when the user asks for advanced interactive behavior. +- If the user needs the exact TOML flags involved, pair this page + with [Configuration Reference](configuration-reference.md). + +## Fallback policy + +- Prefer stable, static markdown patterns when runtime behavior is uncertain. +- If interactive behavior is required, call out dependency assumptions clearly. + +## Deeper docs + +- [Navigation: instant navigation](https://zensical.org/docs/setup/navigation/#instant-navigation) +- [Navigation: hide the sidebars](https://zensical.org/docs/setup/navigation/#hide-the-sidebars) +- [Basics: use_directory_urls](https://zensical.org/docs/setup/basics/#use_directory_urls) diff --git a/.agents/skills/zensical-site/references/docs-reference-outline.md b/.agents/skills/zensical-site/references/docs-reference-outline.md new file mode 100644 index 00000000..9d1358f1 --- /dev/null +++ b/.agents/skills/zensical-site/references/docs-reference-outline.md @@ -0,0 +1,93 @@ +# Zensical Docs Reference Outline + +Goal: define which docs to reference later when expanding the `zensical-site` skill, and what to +extract from each. + +## 1) Authoring docs to reference + +- [Markdown](https://zensical.org/docs/authoring/markdown/) + - Pull later: baseline markdown rules, internal linking guidance, page-title precedence. +- [Front matter](https://zensical.org/docs/authoring/frontmatter/) + - Pull later: supported front matter keys, precedence/override behavior, layout/search-related + keys. +- [Admonitions](https://zensical.org/docs/authoring/admonitions/) + - Pull later: syntax variants, supported admonition types, required extension/config. +- [Buttons](https://zensical.org/docs/authoring/buttons/) + - Pull later: button syntax/classes, icon usage in buttons, extension requirements. +- [Code blocks](https://zensical.org/docs/authoring/code-blocks/) + - Pull later: fenced code options (titles/line numbers/highlights/annotations/snippets), toggles, + feature flags. +- [Content tabs](https://zensical.org/docs/authoring/content-tabs/) + - Pull later: tab syntax, linked-tab behavior, nesting/anchor constraints. +- [Data tables](https://zensical.org/docs/authoring/data-tables/) + - Pull later: table syntax conventions, sorting integration, compatibility constraints. +- [Diagrams](https://zensical.org/docs/authoring/diagrams/) + - Pull later: Mermaid setup, supported diagram families, customization hooks. +- [Footnotes](https://zensical.org/docs/authoring/footnotes/) + - Pull later: reference/definition syntax, tooltip integration behavior. +- [Formatting](https://zensical.org/docs/authoring/formatting/) + - Pull later: inline formatting patterns, required extensions. +- [Grids](https://zensical.org/docs/authoring/grids/) + - Pull later: card/grid container syntax, nesting rules, extension dependencies. +- [Icons, Emojis](https://zensical.org/docs/authoring/icons-emojis/) + - Pull later: shortcode conventions, icon naming rules, template usage. +- [Images](https://zensical.org/docs/authoring/images/) + - Pull later: alignment/captions/lazy-load patterns, light/dark asset behavior. +- [Lists](https://zensical.org/docs/authoring/lists/) + - Pull later: list-type syntax (unordered/ordered/definition/task), extension requirements. +- [Math](https://zensical.org/docs/authoring/math/) + - Pull later: MathJax vs KaTeX options, delimiter conventions, navigation integration caveats. +- [Tooltips](https://zensical.org/docs/authoring/tooltips/) + - Pull later: tooltip and abbreviation syntax, glossary automation behavior. + +## 2) Cross-reference docs required for authoring correctness + +- [Basics: use_directory_urls](https://zensical.org/docs/setup/basics/#use_directory_urls) + - Pull later: how URL mode changes link generation/resolution. +- [Navigation: instant navigation](https://zensical.org/docs/setup/navigation/#instant-navigation) + - Pull later: runtime navigation behavior impacting JS-backed features (math/diagrams/tables). +- [Navigation: hide the sidebars](https://zensical.org/docs/setup/navigation/#hide-the-sidebars) + - Pull later: sidebar/layout controls that affect page composition and front matter. +- [Site search](https://zensical.org/docs/setup/search/) + - Pull later: metadata/indexing behavior tied to front matter and discoverability. +- [Colors](https://zensical.org/docs/setup/colors/) + - Pull later: theme/palette behavior affecting rendering choices. +- [Logo and icons: additional icons](https://zensical.org/docs/setup/logo-and-icons/#additional-icons) + - Pull later: custom icon registration and usage flow. +- [Python Markdown](https://zensical.org/docs/setup/extensions/python-markdown/) + - Pull later: base Python Markdown extension setup required by authoring features. +- [Python Markdown Extensions](https://zensical.org/docs/setup/extensions/python-markdown-extensions/) + - Pull later: pymdown extension setup/options used across many authoring features. +- [Customization](https://zensical.org/docs/customization/) + - Pull later: CSS/JS/template override mechanisms for behavior and styling adjustments. +- [Customization: additional CSS](https://zensical.org/docs/customization/#additional-css) + - Pull later: custom stylesheet integration points. +- [Customization: additional JavaScript](https://zensical.org/docs/customization/#additional-javascript) + - Pull later: custom JS integration points. +- [Customization: custom templates](https://zensical.org/docs/customization/#custom-templates) + - Pull later: template customization entry points. +- [Customization: configuring overrides](https://zensical.org/docs/customization/#configuring-overrides) + - Pull later: override config structure and placement. +- [Customization: template overrides](https://zensical.org/docs/customization/#template-overrides) + - Pull later: template override behavior and scope. +- [Customization: overriding blocks](https://zensical.org/docs/customization/#overriding-blocks) + - Pull later: block-level extension points. +- [Customization: extending the theme](https://zensical.org/docs/customization/#extending-the-theme) + - Pull later: theme extension strategy and boundaries. + +## 3) Extraction template for the next pass + +Use these fields when we return to pull details: + +- `doc_url` +- `doc_title` +- `feature_area` +- `authoring_purpose` +- `required_config` +- `syntax_patterns` +- `options_modifiers` +- `dependencies` +- `behavioral_rules` +- `limitations_edge_cases` +- `cross_reference_urls` +- `minimal_examples_to_capture` diff --git a/.agents/skills/zensical-site/references/shared-patterns.md b/.agents/skills/zensical-site/references/shared-patterns.md new file mode 100644 index 00000000..414eecce --- /dev/null +++ b/.agents/skills/zensical-site/references/shared-patterns.md @@ -0,0 +1,40 @@ +# Shared Patterns + +Use this page for rules that apply across multiple subject references. + +## Link handling + +- Use relative links for internal docs navigation. +- Link to markdown sources/pages, not built HTML artifacts. +- Use meaningful anchor text. +- Avoid absolute links unless the destination is external. + +## Title and structure defaults + +- Use one top-level heading per page. +- Keep heading hierarchy consistent (`#`, `##`, `###`). +- Keep paragraphs short and scannable. + +## Minimal example standard + +For each feature example: + +- show the smallest valid syntax pattern +- include only one meaningful option at a time +- avoid stacking unrelated options in one block + +## Failure prevention checklist + +Before final output, quickly check: + +- internal links are relative and valid for docs context +- front matter and body title do not conflict +- feature syntax matches the relevant subject reference +- required prerequisites are called out when needed +- unresolved assumptions are listed in `Open Questions` + +## Escalation rule + +When deeper behavior is needed, do not guess. +Link to the corresponding upstream docs listed +in [Docs Reference Outline](docs-reference-outline.md). diff --git a/.agents/skills/zensical-site/references/subjects/callouts-and-interactive-elements.md b/.agents/skills/zensical-site/references/subjects/callouts-and-interactive-elements.md new file mode 100644 index 00000000..abce173e --- /dev/null +++ b/.agents/skills/zensical-site/references/subjects/callouts-and-interactive-elements.md @@ -0,0 +1,77 @@ +# Subject: Callouts and Interactive Elements + +Includes admonitions, buttons, content tabs, and tooltips. + +## Use this when + +- emphasizing important notes/warnings +- creating action-style links with button presentation +- organizing alternative content paths with tabs +- adding inline clarifications with tooltips/abbreviations + +## Minimum working patterns + +### Admonition + +```markdown +!!! note + Key context for the reader. +``` + +### Button-style link + +```markdown +[Get Started](../get-started/){ .md-button } +``` + +### Content tab + +```markdown +=== "CLI" + Use this command. + +=== "UI" + Use this interface path. +``` + +### Tooltip/abbreviation + +```markdown +The API uses HTML. + +*[HTML]: HyperText Markup Language +``` + +## Required config / prerequisites + +- Most patterns depend on specific markdown extensions. +- Verify extension availability in [Extension Prerequisites](../dependencies/extension-prereqs.md) + when behavior is uncertain. +- Use [Configuration Reference](../dependencies/configuration-reference.md) when the answer should + include TOML examples for tabs, admonitions, buttons, tooltips, or linked tabs. + +## Common options the model may need + +- admonition type (`note`, `warning`, `tip`, etc.) +- button styling classes (if supported) +- tab labels that map to reader context (for example, platform names) + +## Common mistakes to avoid + +- incorrect indentation inside admonitions or tab bodies +- mixing tab syntaxes in one block +- using button classes without ensuring style support +- turning critical instructions into tooltips that hide required info + +## Interactions / caveats + +- Tabs and tooltips may rely on extension support and theme behavior. +- If interaction behavior is runtime-sensitive, + check [Navigation and Runtime Caveats](../dependencies/navigation-runtime-caveats.md). + +## Deeper docs + +- [Admonitions](https://zensical.org/docs/authoring/admonitions/) +- [Buttons](https://zensical.org/docs/authoring/buttons/) +- [Content tabs](https://zensical.org/docs/authoring/content-tabs/) +- [Tooltips](https://zensical.org/docs/authoring/tooltips/) diff --git a/.agents/skills/zensical-site/references/subjects/code-and-technical-content.md b/.agents/skills/zensical-site/references/subjects/code-and-technical-content.md new file mode 100644 index 00000000..d949f4a0 --- /dev/null +++ b/.agents/skills/zensical-site/references/subjects/code-and-technical-content.md @@ -0,0 +1,74 @@ +# Subject: Code and Technical Content + +Includes code blocks, diagrams, and math. + +## Use this when + +- showing commands or source code +- adding architecture or flow diagrams +- writing equations or math-heavy technical docs + +## Minimum working patterns + +### Code block + +````markdown +```bash +zensical build +``` +```` + +### Mermaid diagram + +````markdown +```mermaid +graph TD + A[Start] --> B[Build] +``` +```` + +### Math + +```markdown +Inline: $E=mc^2$ + +Block: +$$ +\int_0^1 x^2 dx +$$ +``` + +## Required config / prerequisites + +- syntax highlighting and advanced code features depend on markdown extensions +- diagrams require Mermaid support +- math rendering requires configured math engine (MathJax/KaTeX path) +- use [Configuration Reference](../dependencies/configuration-reference.md) when the user needs + actual TOML, `extra_javascript`, or `extra_css` examples + +Use [Extension Prerequisites](../dependencies/extension-prereqs.md) for prerequisite mapping. + +## Common options the model may need + +- code title/line number/highlight options +- diagram type selection by intent (flow, sequence, state) +- inline vs block math choice + +## Common mistakes to avoid + +- malformed fence syntax or missing language labels +- using advanced code options without checking support +- combining unsupported Mermaid constructs blindly +- adding math delimiters incompatible with configured renderer + +## Interactions / caveats + +- client-side navigation may require runtime-safe behavior for diagrams/math rendering. +- if runtime behavior is uncertain, + check [Navigation and Runtime Caveats](../dependencies/navigation-runtime-caveats.md). + +## Deeper docs + +- [Code blocks](https://zensical.org/docs/authoring/code-blocks/) +- [Diagrams](https://zensical.org/docs/authoring/diagrams/) +- [Math](https://zensical.org/docs/authoring/math/) diff --git a/.agents/skills/zensical-site/references/subjects/data-and-visualization.md b/.agents/skills/zensical-site/references/subjects/data-and-visualization.md new file mode 100644 index 00000000..2703cb1e --- /dev/null +++ b/.agents/skills/zensical-site/references/subjects/data-and-visualization.md @@ -0,0 +1,52 @@ +# Subject: Data and Visualization + +Includes data tables and structured visual data presentation. + +## Use this when + +- presenting comparable values in table form +- creating compact structured summaries +- adding sortable or interactive table patterns when supported + +## Minimum working pattern + +```markdown +| Metric | Value | +| --- | --- | +| Build time | 42s | +| Pages | 120 | +``` + +## Required config / prerequisites + +- basic tables work in markdown +- sortable/interactive behavior may require extra setup +- use [Configuration Reference](../dependencies/configuration-reference.md) when the answer needs + `extra_javascript` examples or search-related caveats + +Check [Extension Prerequisites](../dependencies/extension-prereqs.md) +and [Navigation and Runtime Caveats](../dependencies/navigation-runtime-caveats.md) for +setup-sensitive behavior. + +## Common options the model may need + +- alignment and readability choices +- compact column labels for small screens +- simple grouping by section before large tables + +## Common mistakes to avoid + +- very wide tables without considering readability +- dense numeric tables without context labels +- assuming sorting is available without support confirmation + +## Interactions / caveats + +- large tables often need surrounding explanation text. +- for complex visualizations, use diagrams guidance + in [Code and Technical Content](code-and-technical-content.md). + +## Deeper docs + +- [Data tables](https://zensical.org/docs/authoring/data-tables/) +- [Diagrams](https://zensical.org/docs/authoring/diagrams/) diff --git a/.agents/skills/zensical-site/references/subjects/inline-formatting-and-microcontent.md b/.agents/skills/zensical-site/references/subjects/inline-formatting-and-microcontent.md new file mode 100644 index 00000000..14bc4379 --- /dev/null +++ b/.agents/skills/zensical-site/references/subjects/inline-formatting-and-microcontent.md @@ -0,0 +1,64 @@ +# Subject: Inline Formatting and Microcontent + +Includes formatting, lists, and footnotes. + +## Use this when + +- improving readability of technical prose +- structuring procedures, requirements, and definitions +- adding supporting references that should not interrupt flow + +## Minimum working patterns + +### Lists + +```markdown +- First item +- Second item + +1. Step one +2. Step two +``` + +### Inline emphasis + +```markdown +Use **bold** for emphasis and `inline code` for commands. +``` + +### Footnote + +```markdown +The behavior depends on configuration.[^1] + +[^1]: Verify setup before publishing. +``` + +## Required config / prerequisites + +- basic lists and emphasis are markdown-native +- some advanced formatting or footnote rendering can depend on extension support + +## Common options the model may need + +- ordered vs unordered lists by intent +- definition/task list patterns when supported +- restrained inline emphasis for scanability + +## Common mistakes to avoid + +- over-formatting simple prose +- mixing list indentation levels incorrectly +- using footnotes for critical instructions that should stay inline +- inconsistent list punctuation/voice within one section + +## Interactions / caveats + +- footnotes and advanced list behavior may vary by extension setup. +- if behavior is unclear, use simpler markdown and add a note. + +## Deeper docs + +- [Formatting](https://zensical.org/docs/authoring/formatting/) +- [Lists](https://zensical.org/docs/authoring/lists/) +- [Footnotes](https://zensical.org/docs/authoring/footnotes/) diff --git a/.agents/skills/zensical-site/references/subjects/layout-and-media.md b/.agents/skills/zensical-site/references/subjects/layout-and-media.md new file mode 100644 index 00000000..3306c7d9 --- /dev/null +++ b/.agents/skills/zensical-site/references/subjects/layout-and-media.md @@ -0,0 +1,59 @@ +# Subject: Layout and Media + +Includes grids, images, and icons/emojis. + +## Use this when + +- building card layouts or visual content blocks +- adding screenshots, diagrams, or illustrative assets +- using iconography to improve scanability + +## Minimum working patterns + +### Image + +```markdown +![Setup screen](../assets/setup-screen.png) +``` + +### Grid/card block + +Use the simplest supported grid/card pattern from upstream docs for the current setup. + +### Icon/emoji + +Use supported shortcode or icon notation from the active icon set. + +## Required config / prerequisites + +- grid and icon behavior can depend on extension support +- some icon sets require explicit setup +- light/dark image swaps may rely on theme conventions + +See [Extension Prerequisites](../dependencies/extension-prereqs.md) +and [Customization Boundaries](../dependencies/customization-boundaries.md). + +## Common options the model may need + +- image caption/alignment/lazy-load behavior +- card layout variants with simple consistent structure +- icon naming conventions for available packs + +## Common mistakes to avoid + +- large unoptimized images without context +- relying on icons not registered in the site setup +- using complex grid syntax when plain sections would communicate better +- mixing visual patterns that reduce readability on small screens + +## Interactions / caveats + +- palette/theme choices can affect icon and image visibility. +- if task requires custom styling tweaks, escalate to customization boundaries. + +## Deeper docs + +- [Grids](https://zensical.org/docs/authoring/grids/) +- [Images](https://zensical.org/docs/authoring/images/) +- [Icons and emojis](https://zensical.org/docs/authoring/icons-emojis/) +- [Logo and icons: additional icons](https://zensical.org/docs/setup/logo-and-icons/#additional-icons) diff --git a/.agents/skills/zensical-site/references/voice-and-tone.md b/.agents/skills/zensical-site/references/voice-and-tone.md new file mode 100644 index 00000000..6aecf423 --- /dev/null +++ b/.agents/skills/zensical-site/references/voice-and-tone.md @@ -0,0 +1,19 @@ +# Voice and Tone (Scaffold) + +Use this file to capture concrete writing patterns from zensical.org. + +## Brand voice notes + +- TODO: Add 5-10 short excerpts from published pages. +- TODO: Note preferred level of formality. +- TODO: Note typical sentence length and pacing. + +## Do/Don't examples + +- Do: TODO +- Don't: TODO + +## Terminology + +- Preferred terms: TODO +- Terms to avoid: TODO diff --git a/.agents/skills/zensical-site/templates/page-draft-template.md b/.agents/skills/zensical-site/templates/page-draft-template.md new file mode 100644 index 00000000..f53d5e86 --- /dev/null +++ b/.agents/skills/zensical-site/templates/page-draft-template.md @@ -0,0 +1,23 @@ +# {{ Page Title }} + +## Summary + +{{ 1-2 sentence summary of the page goal }} + +## Who this is for + +{{ target audience }} + +## Main content + +{{ organized sections with descriptive headings }} + +## Key takeaways + +- {{ takeaway 1 }} +- {{ takeaway 2 }} +- {{ takeaway 3 }} + +## Related links + +- \[{{ link text }}\]({{ url }}) diff --git a/.claude/skills/zensical-site b/.claude/skills/zensical-site new file mode 120000 index 00000000..a86163bf --- /dev/null +++ b/.claude/skills/zensical-site @@ -0,0 +1 @@ +../../.agents/skills/zensical-site \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json index 9d98ce1c..f2894084 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -5,6 +5,11 @@ "source": "LayeredCraft/skills", "sourceType": "github", "computedHash": "fbd78cda46da7d8753beca0d68ce6532589daa4da0a457aa709fe1e11da468fe" + }, + "zensical-site": { + "source": "LayeredCraft/skills", + "sourceType": "github", + "computedHash": "f66b47704234c8242d45a52e3d92d5307f49254e189819165ee37845da64e9c8" } } } From 2d5fd70e2a2e4c2ec2e335933f0242bd6d3143ba Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 17 Apr 2026 20:50:06 -0400 Subject: [PATCH 02/13] feat(docs): update local docs tasks and optimize dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renamed and enhanced local docs tasks (`run-docs` → `docs:serve` and new `docs:build`). - Migrated tasks to use `zensical` for serving and building docs. - Removed deprecated dependency references to clean up `uv.lock`. - Added `deepmerge` as a new dependency in `uv.lock`. --- pyproject.toml | 7 +- tasks/LocalDevTasks.yml | 16 +- uv.lock | 387 ++++------------------------------------ 3 files changed, 53 insertions(+), 357 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 38ba4fa7..814b8aff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,8 @@ name = "minimal-lambda-docs" version = "0.1.0" description = "mkdocs for minimal-lambda" requires-python = ">=3.13" -dependencies = [ - "mkdocs>=1.6.1", - "mkdocs-material>=9.7.0", + +[dependency-groups] +dev = [ + "zensical>=0.0.33", ] diff --git a/tasks/LocalDevTasks.yml b/tasks/LocalDevTasks.yml index 26150a19..50d60db9 100644 --- a/tasks/LocalDevTasks.yml +++ b/tasks/LocalDevTasks.yml @@ -58,9 +58,17 @@ tasks: cmds: - dotnet lambda-test-tool start --lambda-emulator-port 5050 --config-storage-path ./lambda_test_tool - run-docs: - desc: Run MKDocs server locally + docs:serve: + desc: Serve docs locally with Zensical silent: true cmds: - - uv sync - - uv run mkdocs serve --livereload + - echo "📖 Serving Docs" + - uv run zensical serve -f mkdocs.yml + + docs:build: + desc: Build docs site with Zensical + silent: true + cmds: + - echo "📖 Building Docs" + - uv run zensical build -f mkdocs.yml + - echo "✅ Docs built" diff --git a/uv.lock b/uv.lock index cc80055b..830ec114 100644 --- a/uv.lock +++ b/uv.lock @@ -2,79 +2,6 @@ version = 1 revision = 3 requires-python = ">=3.13" -[[package]] -name = "babel" -version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, -] - -[[package]] -name = "backrefs" -version = "6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, - { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, - { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, - { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, -] - -[[package]] -name = "certifi" -version = "2025.11.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, -] - [[package]] name = "click" version = "8.3.1" @@ -97,36 +24,12 @@ wheels = [ ] [[package]] -name = "ghp-import" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" +name = "deepmerge" +version = "2.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, ] [[package]] @@ -138,219 +41,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, ] -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - -[[package]] -name = "mergedeep" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, -] - [[package]] name = "minimal-lambda-docs" version = "0.1.0" source = { virtual = "." } -dependencies = [ - { name = "mkdocs" }, - { name = "mkdocs-material" }, -] - -[package.metadata] -requires-dist = [ - { name = "mkdocs", specifier = ">=1.6.1" }, - { name = "mkdocs-material", specifier = ">=9.7.0" }, -] -[[package]] -name = "mkdocs" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "ghp-import" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mergedeep" }, - { name = "mkdocs-get-deps" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "watchdog" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +[package.dev-dependencies] +dev = [ + { name = "zensical" }, ] -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mergedeep" }, - { name = "platformdirs" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, -] - -[[package]] -name = "mkdocs-material" -version = "9.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "backrefs" }, - { name = "colorama" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "mkdocs" }, - { name = "mkdocs-material-extensions" }, - { name = "paginate" }, - { name = "pygments" }, - { name = "pymdown-extensions" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/3b/111b84cd6ff28d9e955b5f799ef217a17bc1684ac346af333e6100e413cb/mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec", size = 4094546, upload-time = "2025-11-11T08:49:09.73Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/87/eefe8d5e764f4cf50ed91b943f8e8f96b5efd65489d8303b7a36e2e79834/mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887", size = 9283770, upload-time = "2025-11-11T08:49:06.26Z" }, -] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, -] - -[[package]] -name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, -] - -[[package]] -name = "paginate" -version = "0.5.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, -] +[package.metadata] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, -] +[package.metadata.requires-dev] +dev = [{ name = "zensical", specifier = ">=0.0.33" }] [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pymdown-extensions" -version = "10.17.2" +version = "10.21.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/6d/af5378dbdb379fddd9a277f8b9888c027db480cde70028669ebd009d642a/pymdown_extensions-10.17.2.tar.gz", hash = "sha256:26bb3d7688e651606260c90fb46409fbda70bf9fdc3623c7868643a1aeee4713", size = 847344, upload-time = "2025-11-26T15:43:57.004Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/78/b93cb80bd673bdc9f6ede63d8eb5b4646366953df15667eb3603be57a2b1/pymdown_extensions-10.17.2-py3-none-any.whl", hash = "sha256:bffae79a2e8b9e44aef0d813583a8fea63457b7a23643a43988055b7b79b4992", size = 266556, upload-time = "2025-11-26T15:43:55.162Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, ] [[package]] @@ -390,67 +115,29 @@ wheels = [ ] [[package]] -name = "pyyaml-env-tag" -version = "1.1" +name = "zensical" +version = "0.0.33" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "markdown" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, -] - -[[package]] -name = "requests" -version = "2.33.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] - -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/59/c2/dea4b86dc1ca2a7b55414017f12cfb12b5cfdf3a1ed7c77a04c271eb523b/zensical-0.0.33.tar.gz", hash = "sha256:05209cb4f80185c533e0d37c25d084ddc2050e3d5a4dd1b1812961c2ee0c3380", size = 3892278, upload-time = "2026-04-14T11:08:19.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/5f/45d5200405420a9d8ac91cf9e7826622ea12f3198e8e6ac4ffb481eb53bf/zensical-0.0.33-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f658e3c241cfbb560bd8811116a9486cff7e04d7d5aed73569dd533c74187450", size = 12416748, upload-time = "2026-04-14T11:07:43.246Z" }, + { url = "https://files.pythonhosted.org/packages/33/1e/aadaf31d6e4d20419ecedaf0b1c804e359ec23dcdb44c8d2bf6d8407080c/zensical-0.0.33-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f9813ac3256c28e2e2f1ba5c9fab1b4bca62bbe0e0f8e85ac22d33b068b1b08a", size = 12293372, upload-time = "2026-04-14T11:07:46.569Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/838be8451ea8b2aecec39fbec3971060fc705e17f5741249740d9b6a6824/zensical-0.0.33-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bad7ac71028769c5d1f3f84f448dbb7352db28d77095d1b40a8d1b0aa34ec30", size = 12659832, upload-time = "2026-04-14T11:07:50.754Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5c/dd957d7c83efc13a70a6058d4190a3afcf29942aefb391120bca5466347d/zensical-0.0.33-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:06bb039daf044547c9400a52f9493b3cd486ba9baef3324fdcffd2e26e61105f", size = 12603847, upload-time = "2026-04-14T11:07:53.698Z" }, + { url = "https://files.pythonhosted.org/packages/b7/99/dd6ccc392ece1f34fb20ea339a01717badbbeb2fba1d4f3019a5028d0bcc/zensical-0.0.33-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:260238062b3139ece0edab93f4dbe7a12923453091f5aa580dfd73e799388076", size = 12956236, upload-time = "2026-04-14T11:07:56.728Z" }, + { url = "https://files.pythonhosted.org/packages/f4/76/e0a1b884eadf6afa7e2d56c90c268eec36836ac27e96ef250c0129e55417/zensical-0.0.33-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dff0f4afda7b8586bc4ab2a5684bce5b282232dd4e0cad3be4c73fedd264425", size = 12701944, upload-time = "2026-04-14T11:07:59.928Z" }, + { url = "https://files.pythonhosted.org/packages/38/38/e1ff13461e406864fa2b23fc828822659a7dbac5c79398f724d17f088540/zensical-0.0.33-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:207b4d81b208d75b97dc7bd318804550b886a3e852ef67429ef0e6b9442839d1", size = 12835444, upload-time = "2026-04-14T11:08:02.998Z" }, + { url = "https://files.pythonhosted.org/packages/41/04/7d24d52d6903fc5c511633afe8b5716fef19da09685327665cc127f61648/zensical-0.0.33-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:06d2f57f7bc8cc8fd904386020ea1365eebc411e8698a871e9525c885abca574", size = 12878419, upload-time = "2026-04-14T11:08:06.054Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ec/87fc9e360c694ab006363c7834639eccafd0d26a487cd63dd609bd68f36a/zensical-0.0.33-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:c2851b82d83aa0b2ae4f8e99731cfeedeecebfa04e6b3fc4d375deca629fa240", size = 13022474, upload-time = "2026-04-14T11:08:09.007Z" }, + { url = "https://files.pythonhosted.org/packages/10/b3/0bf174ab6ceedb31d9af462073b5339c894b2084a27d42cb9f0906050d76/zensical-0.0.33-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:90daaf512b0429d7b9147ad5e6085b455d24803eff18b508aed738ca65444683", size = 12975233, upload-time = "2026-04-14T11:08:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a9/27/7cc3c2d284698647f60f3b823e0101e619c87edf158d47ee11bf4bfb6228/zensical-0.0.33-cp310-abi3-win32.whl", hash = "sha256:2701820597fe19361a12371129927c58c19633dcaa5f6986d610dce58cecd8c4", size = 12012664, upload-time = "2026-04-14T11:08:14.977Z" }, + { url = "https://files.pythonhosted.org/packages/25/0b/6be5c2fdaf9f1600577e7ba5e235d86b72a26f6af389efb146f978f76ac3/zensical-0.0.33-cp310-abi3-win_amd64.whl", hash = "sha256:a5a0911b4247708a55951b74c459f4d5faec5daaf287d23a2e1f0d96be1e647f", size = 12206255, upload-time = "2026-04-14T11:08:17.375Z" }, ] From 6353355f2e3c2f5e9d11ec5d5198ca2595489390 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 17 Apr 2026 20:51:19 -0400 Subject: [PATCH 03/13] chore(docs): remove SonarCloud quality gate badge from README - Cleaned up outdated SonarCloud badge link from README for better clarity. - Retained other essential badges for build and license status. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f7937f6a..88a3462a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Main Build](https://github.com/j-d-ha/minimal-lambda/actions/workflows/main-build.yaml/badge.svg)](https://github.com/j-d-ha/minimal-lambda/actions/workflows/main-build.yaml) [![codecov](https://codecov.io/gh/j-d-ha/minimal-lambda/graph/badge.svg?token=BWORPTQ0UK)](https://codecov.io/gh/j-d-ha/minimal-lambda) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=j-d-ha_minimal-lambda&metric=alert_status&token=9fb519975d91379dcfbc6c13a4bd4207131af6e3)](https://sonarcloud.io/summary/new_code?id=j-d-ha_minimal-lambda) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) **Lambda-first hosting with Minimal API-inspired patterns** – Familiar .NET ergonomics with From fc20c3c92011b5deeb38694987200a8d927897a9 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 17 Apr 2026 20:51:27 -0400 Subject: [PATCH 04/13] chore(docs): remove outdated changelog and reformat index content - Deleted the unused `changelog.md` file to reduce redundancy in documentation. - Reorganized and re-indented content in `index.md` for enhanced readability and clarity. - Fixed various formatting inconsistencies in tables and lists for a cohesive style. - Updated callout and button links for better navigation within the docs. --- docs/changelog.md | 1 - docs/index.md | 68 +++++++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 30 deletions(-) delete mode 100644 docs/changelog.md diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index 90cb31c6..00000000 --- a/docs/changelog.md +++ /dev/null @@ -1 +0,0 @@ ---8<-- "CHANGELOG.md" \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index b75d6f34..791e0100 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,12 +6,16 @@ title: "" [![Main Build](https://github.com/j-d-ha/minimal-lambda/actions/workflows/main-build.yaml/badge.svg)](https://github.com/j-d-ha/minimal-lambda/actions/workflows/main-build.yaml) [![codecov](https://codecov.io/gh/j-d-ha/minimal-lambda/graph/badge.svg?token=BWORPTQ0UK)](https://codecov.io/gh/j-d-ha/minimal-lambda) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=j-d-ha_minimal-lambda&metric=alert_status&token=9fb519975d91379dcfbc6c13a4bd4207131af6e3)](https://sonarcloud.io/summary/new_code?id=j-d-ha_minimal-lambda) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/j-d-ha/minimal-lambda/blob/main/LICENSE) **Minimal API-inspired, Lambda-first hosting.** -MinimalLambda keeps the builder, DI, middleware, and handler mapping patterns you know from ASP.NET Core, but is built from the ground up for **any Lambda event source** (SQS, SNS, API Gateway, Kinesis, S3, EventBridge, etc.). Strongly-typed envelopes, lifecycle hooks, scoped invocations, source generation, and cancellation-token handling shape those familiar patterns to Lambda’s execution model—so you get type-safe events, per-invocation scopes, predictable timeouts, and a smoother developer experience when iterating locally or in CI. +MinimalLambda keeps the builder, DI, middleware, and handler mapping patterns you know from ASP.NET +Core, but is built from the ground up for **any Lambda event source** (SQS, SNS, API Gateway, +Kinesis, S3, EventBridge, etc.). Strongly-typed envelopes, lifecycle hooks, scoped invocations, +source generation, and cancellation-token handling shape those familiar patterns to Lambda’s +execution model—so you get type-safe events, per-invocation scopes, predictable timeouts, and a +smoother developer experience when iterating locally or in CI. [Get Started](getting-started/index.md){ .md-button .md-button--primary } [Guides](guides/index.md){ .md-button } @@ -21,7 +25,8 @@ MinimalLambda keeps the builder, DI, middleware, and handler mapping patterns yo ## Why MinimalLambda? -Stop wiring up DI scopes, serializers, and cancellation tokens by hand. Ship features with patterns you +Stop wiring up DI scopes, serializers, and cancellation tokens by hand. Ship features with patterns +you already know, while still embracing Lambda’s execution model. === "Traditional Lambda" @@ -174,8 +179,8 @@ await lambda.RunAsync(); ``` !!! tip "Next Steps" - Ready to dive deeper? Check out the [Getting Started Guide](getting-started/index.md) for a complete - tutorial, or explore the [Examples](examples/index.md) to see real-world applications. +Ready to dive deeper? Check out the [Getting Started Guide](getting-started/index.md) for a complete +tutorial, or explore the [Examples](examples/index.md) to see real-world applications. --- @@ -186,11 +191,11 @@ await lambda.RunAsync(); The core packages provide the fundamental hosting framework, abstractions, and observability support for building AWS Lambda functions. -| Package | Description | NuGet | Downloads | -|------------------------------------------------------------------|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| -| [**MinimalLambda**](https://github.com/j-d-ha/minimal-lambda/tree/main/src/MinimalLambda) | Core hosting framework with middleware and DI | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | +| Package | Description | NuGet | Downloads | +|---------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| +| [**MinimalLambda**](https://github.com/j-d-ha/minimal-lambda/tree/main/src/MinimalLambda) | Core hosting framework with middleware and DI | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](https://github.com/j-d-ha/minimal-lambda/tree/main/src/MinimalLambda.Abstractions) | Core interfaces and contracts | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | -| [**MinimalLambda.OpenTelemetry**](features/open_telemetry.md) | Distributed tracing and observability | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.OpenTelemetry**](features/open_telemetry.md) | Distributed tracing and observability | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | ### Envelope Packages @@ -198,23 +203,23 @@ Envelope packages provide type-safe handling of AWS Lambda event sources with au deserialization. !!! info "What are Envelopes?" - Envelopes wrap AWS Lambda events with strongly-typed payload handling, giving you compile-time type - safety and automatic deserialization of message bodies from SQS, SNS, Kinesis, and other event - sources. +Envelopes wrap AWS Lambda events with strongly-typed payload handling, giving you compile-time type +safety and automatic deserialization of message bodies from SQS, SNS, Kinesis, and other event +sources. [Learn more about envelopes](features/envelopes.md){ .md-button } -| Package | Description | NuGet | Downloads | -|----------------------------------------------------------------------------------------|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **MinimalLambda.Envelopes** | Infrastructure package for HTTP response builders | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | -| **MinimalLambda.Envelopes.Sqs** | Simple Queue Service events with typed message bodies | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | -| **MinimalLambda.Envelopes.Sns** | Simple Notification Service messages | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | -| **MinimalLambda.Envelopes.ApiGateway** | REST, HTTP, and WebSocket APIs | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | -| **MinimalLambda.Envelopes.Kinesis** | Data Streams with typed records | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Kinesis.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kinesis) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Kinesis.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kinesis/) | +| Package | Description | NuGet | Downloads | +|---------------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **MinimalLambda.Envelopes** | Infrastructure package for HTTP response builders | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | +| **MinimalLambda.Envelopes.Sqs** | Simple Queue Service events with typed message bodies | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | +| **MinimalLambda.Envelopes.Sns** | Simple Notification Service messages | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | +| **MinimalLambda.Envelopes.ApiGateway** | REST, HTTP, and WebSocket APIs | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | +| **MinimalLambda.Envelopes.Kinesis** | Data Streams with typed records | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Kinesis.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kinesis) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Kinesis.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kinesis/) | | **MinimalLambda.Envelopes.KinesisFirehose** | Data transformation | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.KinesisFirehose.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.KinesisFirehose) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.KinesisFirehose.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.KinesisFirehose/) | -| **MinimalLambda.Envelopes.Kafka** | MSK and self-managed Kafka | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Kafka.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kafka) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Kafka.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kafka/) | -| **MinimalLambda.Envelopes.CloudWatchLogs** | Log subscriptions | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.CloudWatchLogs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.CloudWatchLogs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.CloudWatchLogs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.CloudWatchLogs/) | -| **MinimalLambda.Envelopes.Alb** | Application Load Balancer requests | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Alb.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Alb) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Alb.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Alb/) | +| **MinimalLambda.Envelopes.Kafka** | MSK and self-managed Kafka | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Kafka.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kafka) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Kafka.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kafka/) | +| **MinimalLambda.Envelopes.CloudWatchLogs** | Log subscriptions | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.CloudWatchLogs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.CloudWatchLogs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.CloudWatchLogs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.CloudWatchLogs/) | +| **MinimalLambda.Envelopes.Alb** | Application Load Balancer requests | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Alb.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Alb) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Alb.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Alb/) | [Browse all envelope packages](features/envelopes.md){ .md-button } @@ -222,7 +227,8 @@ deserialization. ## Examples & Use Cases -Explore the repository’s `examples/` folder and the docs’ [Examples](examples/index.md) page (content coming +Explore the repository’s `examples/` folder and the docs’ [Examples](examples/index.md) page ( +content coming soon) for end-to-end Lambda samples that wire up middleware, envelopes, and DI. [Examples (Coming Soon)](examples/index.md){ .md-button } @@ -233,22 +239,26 @@ soon) for end-to-end Lambda samples that wire up middleware, envelopes, and DI. ### Get Involved -- **[GitHub Repository](https://github.com/j-d-ha/minimal-lambda)** – Source code, issues, and discussions. -- **[Changelog](changelog.md)** – Version history and release notes. +- **[GitHub Repository](https://github.com/j-d-ha/minimal-lambda)** – Source code, issues, and + discussions. - **[License](https://github.com/j-d-ha/minimal-lambda/blob/main/LICENSE)** – MIT License. ### Documentation - **[Getting Started](getting-started/index.md)** – Installation and first Lambda tutorial. -- **[Guides](guides/index.md)** – In-depth docs on DI, middleware, lifecycle, configuration, and more. +- **[Guides](guides/index.md)** – In-depth docs on DI, middleware, lifecycle, configuration, and + more. - **[Features](features/index.md)** – Envelopes, OpenTelemetry integration, and other add-ons. -- **[Advanced Topics](advanced/index.md)** – Coming soon: AOT, source generators, performance tuning. +- **[Advanced Topics](advanced/index.md)** – Coming soon: AOT, source generators, performance + tuning. ### Support - Ask or search in [GitHub Discussions](https://github.com/j-d-ha/minimal-lambda/discussions). -- File bugs or feature requests via [GitHub Issues](https://github.com/j-d-ha/minimal-lambda/issues). +- File bugs or feature requests + via [GitHub Issues](https://github.com/j-d-ha/minimal-lambda/issues). --- -**Ready to modernize your Lambda development?** [Get started now](getting-started/index.md){ .md-button .md-button--primary } +**Ready to modernize your Lambda development?** [Get started now](getting-started/index.md){ +.md-button .md-button--primary } From 06961992fbaba892e426d80a63f3ab555c82c243 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 17 Apr 2026 20:52:20 -0400 Subject: [PATCH 05/13] chore(docs): remove changelog entry from navigation - Deleted the `Changelog` entry from `mkdocs.yml` navigation to align with recent cleanup. - Ensured navigation remains consistent by maintaining proper structure after removal. --- mkdocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 54a81bb6..1de607d3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,7 +96,6 @@ watch: nav: - Home: - index.md - - Changelog: changelog.md - Getting Started: - getting-started/index.md - Installation: getting-started/installation.md From d8a88779911ed3aa2a1831abd686853378878671 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 18 Apr 2026 14:52:47 -0400 Subject: [PATCH 06/13] chore(docs): cleanup and update workflow for docs build - Removed `pngquant` installation as it's unused in the current workflow. - Replaced `mkdocs build` with `zensical build` to align with updated tooling. - Commented out the Python setup step to simplify and streamline the workflow. --- .github/workflows/docs.yaml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index fdc6a286..c3da0dec 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -38,14 +38,9 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 - - - name: Install pngquant - run: sudo apt-get update && sudo apt-get install -y pngquant - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version-file: "pyproject.toml" + + # - name: Install pngquant + # run: sudo apt-get update && sudo apt-get install -y pngquant - name: Install uv uses: astral-sh/setup-uv@v7 @@ -61,7 +56,7 @@ jobs: - name: Build documentation run: | - uv run mkdocs build --clean + uv run zensical build --clean - name: Upload artifact uses: actions/upload-pages-artifact@v5 From 037e56be5cea3afd276111305c3febc9b4612b27 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 18 Apr 2026 15:02:19 -0400 Subject: [PATCH 07/13] chore(docs): remove commented pngquant installation from workflow - Cleaned up unused and commented `pngquant` installation in `docs.yaml` workflow file. - Retained `uv` installation for workflow continuity. --- .github/workflows/docs.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index c3da0dec..c81e3244 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -38,9 +38,6 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 - - # - name: Install pngquant - # run: sudo apt-get update && sudo apt-get install -y pngquant - name: Install uv uses: astral-sh/setup-uv@v7 From 0b790d2f7579075151b88981ef94a76ec9514e3b Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 8 May 2026 16:30:46 -0400 Subject: [PATCH 08/13] feat: add MinimalLambda agent skill --- README.md | 24 ++ docs/index.md | 17 ++ .../iteration-1/validation-report.md | 53 +++++ skills/minimal-lambda/SKILL.md | 89 ++++++++ skills/minimal-lambda/evals/evals.json | 59 +++++ .../references/best-practices.md | 141 ++++++++++++ .../references/client-project-setup.md | 155 +++++++++++++ .../minimal-lambda/references/core-hosting.md | 209 ++++++++++++++++++ skills/minimal-lambda/references/envelopes.md | 102 +++++++++ .../references/opentelemetry.md | 85 +++++++ .../references/patterns/aot-and-envelopes.md | 73 ++++++ .../references/patterns/envelope-patterns.md | 98 ++++++++ .../references/patterns/handler-patterns.md | 97 ++++++++ .../patterns/middleware-patterns.md | 117 ++++++++++ .../references/patterns/testing-patterns.md | 93 ++++++++ .../references/repo-workflow.md | 95 ++++++++ skills/minimal-lambda/references/testing.md | 98 ++++++++ .../references/troubleshooting.md | 106 +++++++++ .../scripts/validate_references.py | 100 +++++++++ 19 files changed, 1811 insertions(+) create mode 100644 skills/minimal-lambda-workspace/iteration-1/validation-report.md create mode 100644 skills/minimal-lambda/SKILL.md create mode 100644 skills/minimal-lambda/evals/evals.json create mode 100644 skills/minimal-lambda/references/best-practices.md create mode 100644 skills/minimal-lambda/references/client-project-setup.md create mode 100644 skills/minimal-lambda/references/core-hosting.md create mode 100644 skills/minimal-lambda/references/envelopes.md create mode 100644 skills/minimal-lambda/references/opentelemetry.md create mode 100644 skills/minimal-lambda/references/patterns/aot-and-envelopes.md create mode 100644 skills/minimal-lambda/references/patterns/envelope-patterns.md create mode 100644 skills/minimal-lambda/references/patterns/handler-patterns.md create mode 100644 skills/minimal-lambda/references/patterns/middleware-patterns.md create mode 100644 skills/minimal-lambda/references/patterns/testing-patterns.md create mode 100644 skills/minimal-lambda/references/repo-workflow.md create mode 100644 skills/minimal-lambda/references/testing.md create mode 100644 skills/minimal-lambda/references/troubleshooting.md create mode 100755 skills/minimal-lambda/scripts/validate_references.py diff --git a/README.md b/README.md index 88a3462a..b84a6c44 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,30 @@ See the [examples directory](./examples/) for more complete examples, including OpenTelemetry integration. For in-memory integration tests, use [MinimalLambda.Testing](./docs/guides/testing.md) (a `WebApplicationFactory`-style runtime shim). +## AI Agent Skill + +This repository includes a `minimal-lambda` agent skill with focused guidance for building, +debugging, testing, and reviewing MinimalLambda code without loading the full repository into agent +context. + +Install it with [skills.sh](https://skills.sh): + +```bash +npx skills add j-d-ha/minimal-lambda --skill minimal-lambda +``` + +Install globally for Claude Code: + +```bash +npx skills add j-d-ha/minimal-lambda --skill minimal-lambda --global --agent claude-code +``` + +Update later with: + +```bash +npx skills update minimal-lambda +``` + ## Documentation - [MinimalLambda](./src/MinimalLambda/README.md) – Core framework documentation diff --git a/docs/index.md b/docs/index.md index 791e0100..60574e60 100644 --- a/docs/index.md +++ b/docs/index.md @@ -182,6 +182,23 @@ await lambda.RunAsync(); Ready to dive deeper? Check out the [Getting Started Guide](getting-started/index.md) for a complete tutorial, or explore the [Examples](examples/index.md) to see real-world applications. +## AI Agent Skill + +Use the bundled `minimal-lambda` agent skill to give coding agents focused MinimalLambda guidance +for handlers, envelopes, middleware, AOT, testing, and repo workflow. + +Install it with [skills.sh](https://skills.sh): + +```bash +npx skills add j-d-ha/minimal-lambda --skill minimal-lambda +``` + +For a global Claude Code install: + +```bash +npx skills add j-d-ha/minimal-lambda --skill minimal-lambda --global --agent claude-code +``` + --- ## Packages diff --git a/skills/minimal-lambda-workspace/iteration-1/validation-report.md b/skills/minimal-lambda-workspace/iteration-1/validation-report.md new file mode 100644 index 00000000..d7f45f87 --- /dev/null +++ b/skills/minimal-lambda-workspace/iteration-1/validation-report.md @@ -0,0 +1,53 @@ +# MinimalLambda skill validation report — iteration 1 + +## Scope + +Reviewed `skills/minimal-lambda` as dissemination skill for client-project agents, not repo-local `.agents` skill. + +## Validation method + +- Checked skill structure for progressive disclosure. +- Cross-checked referenced docs/source files in repo. +- Verified key API symbols exist in source: + - `LambdaApplication` + - `MapHandler` + - `FromEventAttribute` + - `AddLambdaSerializerWithContext` + - `ConfigureEnvelopeOptions` + - `LambdaApplicationFactory` + - common envelope types +- Added prompt eval set in `skills/minimal-lambda/evals/evals.json` for future skill-creator runs. +- Ran deterministic validation script: + +```bash +python3 skills/minimal-lambda/scripts/validate_references.py +``` + +Result: + +```text +OK: MinimalLambda skill references validated +``` + +## Content improvements made + +Added best-practice/project-use files: + +- `references/best-practices.md` — architecture, DI lifetimes, middleware/lifecycle/testing/AOT checklists. +- `references/client-project-setup.md` — package setup, `Program.cs` template, config, top-level Program testing note, AOT context. +- `references/troubleshooting.md` — generator/runtime/serialization/testing failure playbook. +- `references/patterns/handler-patterns.md` — thin handlers, no-event handlers, keyed services, direct unit tests. +- `references/patterns/middleware-patterns.md` — inline/class middleware, features, short-circuit cache, error boundary. +- `references/patterns/envelope-patterns.md` — exact envelope type table and trigger examples. +- `references/patterns/testing-patterns.md` — `LambdaApplicationFactory`, overrides, invocation APIs, fixture guidance. +- `references/patterns/aot-and-envelopes.md` — serializer context and envelope options patterns. + +Updated: + +- `SKILL.md` task routing now points to new best-practice/pattern/troubleshooting files. +- `references/envelopes.md` now includes exact common envelope type names and AOT serializer guidance. +- `references/core-hosting.md` now points at exact feature helper source file. + +## Gaps / future eval + +Full with-skill vs baseline evals were not run because this harness exposes no subagent task tool. Evals are ready in `evals/evals.json` for a future skill-creator runner. Deterministic reference validation passed. diff --git a/skills/minimal-lambda/SKILL.md b/skills/minimal-lambda/SKILL.md new file mode 100644 index 00000000..f5b9d67a --- /dev/null +++ b/skills/minimal-lambda/SKILL.md @@ -0,0 +1,89 @@ +--- +name: minimal-lambda +description: Work effectively with MinimalLambda, the Lambda-first .NET hosting framework in this repo and in client projects. Use this skill whenever the user asks to build, debug, migrate, test, document, or review code using MinimalLambda APIs, envelopes, middleware, lifecycle hooks, source-generated handlers, AOT/trimming, OpenTelemetry, or MinimalLambda.Testing. Trigger even when the user only mentions AWS Lambda with Minimal API-style .NET patterns, MapHandler, FromEvent, LambdaApplication, or MinimalLambda package names. +--- + +# MinimalLambda skill + +Use this skill to give agents enough MinimalLambda project context without loading entire repo/docs. + +## First move + +1. Identify task area: + - client project setup/package/config template → read `references/client-project-setup.md` + - app setup/handler/DI/lifecycle → read `references/core-hosting.md` and `references/best-practices.md` + - handler shape/unit-testable handlers → read `references/patterns/handler-patterns.md` + - middleware/features/context → read `references/core-hosting.md` and `references/patterns/middleware-patterns.md` + - SQS/SNS/API Gateway/Kinesis/Firehose/Kafka/CloudWatch/ALB envelopes → read `references/envelopes.md` and `references/patterns/envelope-patterns.md` + - Native AOT/trimming/serializer context → read `references/patterns/aot-and-envelopes.md` + - integration tests/client project tests → read `references/testing.md` and `references/patterns/testing-patterns.md` + - tracing/metrics/shutdown flush → read `references/opentelemetry.md` + - compile/runtime/test failure → read `references/troubleshooting.md` + - repo contribution/source generator/AOT work → read `references/repo-workflow.md` +2. Validate against local docs/code when in MinimalLambda repo. Prefer docs first, then source/tests for exact API. +3. Keep Lambda-first constraints in mind: source generation, AOT friendliness, scoped per-invocation services, one handler per runtime execution. + +## Fast mental model + +MinimalLambda = ASP.NET Core Minimal API ergonomics adapted to AWS Lambda: + +```csharp +var builder = LambdaApplication.CreateBuilder(); +builder.Services.AddScoped(); + +await using var lambda = builder.Build(); +lambda.MapHandler(([FromEvent] MyEvent evt, IMyService service, CancellationToken ct) => + service.HandleAsync(evt, ct)); + +await lambda.RunAsync(); +``` + +Core pieces: + +- `LambdaApplication.CreateBuilder()` creates standard .NET host/config/DI defaults. +- `MapHandler(...)` registers one Lambda handler. Source generator intercepts it at compile time. +- `[FromEvent]` marks deserialized event payload. At most one payload parameter. +- Other handler parameters resolve from DI/context/keyed services/cancellation token. +- Middleware wraps invocation pipeline via inline `UseMiddleware(...)` or class `UseMiddleware()`. +- `OnInit(...)` runs once during cold start; `OnShutdown(...)` runs during teardown. +- `MinimalLambda.Testing` runs real pipeline in memory for client project tests. +- Envelope packages provide trigger-specific typed event/body access; use matching package rather than hand-parsing AWS records. + +## Source-of-truth files + +When details matter, inspect these: + +- docs index: `docs/` +- package READMEs: `src/*/README.md`, `src/Envelopes/*/README.md` +- core runtime: `src/MinimalLambda/` +- abstractions: `src/MinimalLambda.Abstractions/` +- source generator: `src/MinimalLambda.SourceGenerators/` +- tests: `tests/` +- examples: `examples/` + +Use `rg` for exact APIs before changing code. Existing docs can lag implementation; code/tests win. + +## Common advice patterns + +Read `references/best-practices.md` before giving architectural advice. + +- Prefer thin handlers delegating to injected services. +- Prefer `CancellationToken` in async handlers and downstream calls. +- Prefer scoped services for per-invocation state; singleton for reusable clients/caches. +- Avoid storing scoped services in singletons. +- Prefer typed records/responses/envelopes over anonymous response contracts. +- Keep AOT/trimming safe: avoid reflection-heavy dynamic paths unless guarded and tested. +- Use method-group handlers or static handler methods when unit-testing handler logic directly. +- For end-to-end behavior, use `LambdaApplicationFactory`. + +## Validation checklist + +Before final answer or patch: + +- Does code compile with source generation? `MapHandler` signature has 0 or 1 `[FromEvent]`. +- Does runtime call only one handler mapping path? +- Are packages matched (`MinimalLambda.Testing` same version as `MinimalLambda`)? +- Are envelope package/type and AWS trigger type aligned? +- Are middleware registered before `MapHandler`? +- Are cancellation tokens propagated? +- For repo changes: run format/tests per `AGENTS.md` when practical. diff --git a/skills/minimal-lambda/evals/evals.json b/skills/minimal-lambda/evals/evals.json new file mode 100644 index 00000000..785ce14e --- /dev/null +++ b/skills/minimal-lambda/evals/evals.json @@ -0,0 +1,59 @@ +{ + "skill_name": "minimal-lambda", + "evals": [ + { + "id": 1, + "prompt": "I have a .NET Lambda using MinimalLambda. Create a clean Program.cs for an order processor with DI, a static method-group handler, cancellation token propagation, and a unit-testable service boundary. Also mention what package/usings I need.", + "expected_output": "Uses LambdaApplication.CreateBuilder, registers services before Build, maps exactly one method-group handler with one [FromEvent] OrderRequest parameter, injects IOrderService and CancellationToken, calls RunAsync, and explains MinimalLambda package/usings.", + "files": [], + "assertions": [ + { "text": "Output uses method-group handler pattern rather than inline business logic." }, + { + "text": "Handler signature has exactly one [FromEvent] payload parameter and propagates CancellationToken." + }, + { "text": "DI registration happens before builder.Build()." }, + { "text": "Output does not recommend manual JSON parsing or reflection-based dispatch." } + ] + }, + { + "id": 2, + "prompt": "We're building an API Gateway HTTP API v2 Lambda with MinimalLambda and Native AOT. Show the request/response envelope pattern and serializer setup for CreateOrderRequest/CreateOrderResponse.", + "expected_output": "Selects MinimalLambda.Envelopes.ApiGateway, uses ApiGatewayV2RequestEnvelope and ApiGatewayV2ResponseEnvelope or ApiGatewayV2Result, includes JsonSerializerContext entries for envelope and payload/response, registers AddLambdaSerializerWithContext and ConfigureEnvelopeOptions.", + "files": [], + "assertions": [ + { "text": "Output chooses API Gateway v2 envelope/result types, not v1-only types." }, + { "text": "Output registers AddLambdaSerializerWithContext()." }, + { "text": "Output configures envelope options TypeInfoResolver for nested body content." }, + { + "text": "Output includes both envelope and payload/response types in JsonSerializerContext." + } + ] + }, + { + "id": 3, + "prompt": "My MinimalLambda middleware isn't seeing the order request and sometimes scoped data leaks between invocations. Review likely causes and show a better middleware pattern.", + "expected_output": "Explains middleware order before MapHandler, uses ILambdaInvocationContext feature helpers like TryGetEvent/GetResponse, distinguishes Items vs Properties, warns against singleton/scoped leaks, and shows thin middleware pattern.", + "files": [], + "assertions": [ + { "text": "Output says middleware should be registered before MapHandler." }, + { "text": "Output uses context.TryGetEvent() or features for typed event access." }, + { + "text": "Output distinguishes per-invocation Items from cross-invocation Properties/singletons." + }, + { "text": "Output warns against capturing scoped services/state in singletons." } + ] + }, + { + "id": 4, + "prompt": "Add integration tests for a MinimalLambda function. I need to override IOrderService in tests, invoke a typed event, assert success, and know when not to use a shared fixture.", + "expected_output": "Uses MinimalLambda.Testing LambdaApplicationFactory, WithHostBuilder ConfigureServices override, InvokeAsync, WasSuccess assertion, and warns shared factory reuses OnInit/singletons.", + "files": [], + "assertions": [ + { "text": "Output uses LambdaApplicationFactory from MinimalLambda.Testing." }, + { "text": "Output overrides services with WithHostBuilder/ConfigureServices." }, + { "text": "Output invokes typed event using InvokeAsync()." }, + { "text": "Output warns shared fixtures reuse OnInit and singleton state." } + ] + } + ] +} diff --git a/skills/minimal-lambda/references/best-practices.md b/skills/minimal-lambda/references/best-practices.md new file mode 100644 index 00000000..482e7e14 --- /dev/null +++ b/skills/minimal-lambda/references/best-practices.md @@ -0,0 +1,141 @@ +# Best practices and decision guide + +Read when designing client-project code, reviewing MinimalLambda usage, or deciding between handler/middleware/lifecycle/testing/envelope patterns. + +## Default architecture + +Use three layers: + +1. `Program.cs` wires host, configuration, services, middleware, lifecycle hooks, and one handler. +2. Handler method adapts Lambda event to application service call. +3. Application services contain business logic and can be unit-tested without Lambda host. + +Good shape: + +```csharp +var builder = LambdaApplication.CreateBuilder(); + +builder.Services.AddScoped(); + +await using var lambda = builder.Build(); + +lambda.MapHandler(OrderHandlers.HandleAsync); + +await lambda.RunAsync(); + +internal static class OrderHandlers +{ + public static Task HandleAsync( + [FromEvent] OrderRequest request, + IOrderService orders, + CancellationToken ct) => + orders.ProcessAsync(request, ct); +} +``` + +Why: source generator sees stable handler signature, handler stays testable, Lambda-specific code stays at edge. + +## Handler best practices + +Prefer: + +- one clear `[FromEvent]` payload parameter when event exists +- explicit typed request/response records +- method-group handlers for direct unit tests +- `CancellationToken` in async handlers +- injected services instead of resolving from `IServiceProvider` +- throwing meaningful exceptions for unrecoverable invalid state + +Avoid: + +- anonymous response contracts in public APIs +- multiple runtime `MapHandler` calls +- manually parsing event JSON when envelope package exists +- reflection-heavy dispatch/routing inside one Lambda unless absolutely needed +- storing `ILambdaInvocationContext` or scoped services beyond invocation + +## DI lifetime choices + +| Need | Lifetime | +| ------------------------------------------------ | --------- | +| AWS SDK client, `HttpClient`, config cache | singleton | +| per-invocation repository/unit of work/DbContext | scoped | +| stateless lightweight helper | transient | + +Never capture scoped services in singleton state. Lambda warm reuse makes leaks harder to notice. + +## Middleware best practices + +Use middleware for cross-cutting invocation concerns: + +- logging scopes/correlation +- auth/authz +- validation +- metrics/tracing +- idempotency/cache short-circuiting +- error mapping + +Ordering: + +1. diagnostics/tracing/logging +2. auth/authz +3. validation +4. idempotency/caching +5. handler + +Keep inline middleware thin. Extract reusable or stateful logic to `ILambdaMiddleware` classes. + +## Lifecycle best practices + +Use `OnInit` for cold-start work: + +- warm caches +- validate required configuration/secrets +- pre-create expensive singleton clients only when needed + +Use `OnShutdown` for bounded cleanup: + +- flush telemetry +- drain buffers +- release external leases + +Keep both cancellation-aware. `OnInit` failures should be intentional because failed init prevents serving invocations. + +## Event source decision guide + +- Plain JSON event: `MinimalLambda` only, `[FromEvent] MyEvent`. +- HTTP API/API Gateway/ALB with JSON body: use matching envelope package and response/result type. +- SQS/SNS/Kinesis/Kafka/Firehose/CloudWatch Logs: use matching envelope package to avoid hand-parsing records. +- Native AOT + envelopes: add `JsonSerializerContext`, `AddLambdaSerializerWithContext()`, and `ConfigureEnvelopeOptions`. + +## Testing strategy + +- Unit-test services and static handler methods directly. +- Use `MinimalLambda.Testing` for pipeline behavior: source-generated binding, middleware, DI scopes, lifecycle, envelopes, serialization, error payloads. +- Share `LambdaApplicationFactory` only when singleton/lifecycle sharing is acceptable. + +## AOT and trimming + +Prefer: + +- source-generated JSON contexts +- static handler methods +- explicit contracts +- package APIs built for source generation + +Avoid: + +- runtime reflection over handler signatures +- dynamic serialization polymorphism without source-gen metadata +- broad service locator patterns that hide dependencies from code review + +## Code review checklist + +- [ ] One runtime handler mapping. +- [ ] Payload parameter has exactly one `[FromEvent]` or no payload at all. +- [ ] Middleware registered before handler mapping. +- [ ] Async work accepts and propagates cancellation token. +- [ ] DI lifetimes match Lambda warm-container reuse. +- [ ] Envelope package matches AWS trigger. +- [ ] Native AOT path has serializer context and envelope options. +- [ ] Integration tests cover real pipeline when framework behavior matters. diff --git a/skills/minimal-lambda/references/client-project-setup.md b/skills/minimal-lambda/references/client-project-setup.md new file mode 100644 index 00000000..567da0d0 --- /dev/null +++ b/skills/minimal-lambda/references/client-project-setup.md @@ -0,0 +1,155 @@ +# Client project setup + +Read when creating or modifying a consumer project that uses MinimalLambda packages. + +## Minimal packages + +Plain Lambda handler: + +```bash +dotnet add package MinimalLambda +``` + +Testing: + +```bash +dotnet add package MinimalLambda.Testing +``` + +OpenTelemetry: + +```bash +dotnet add package MinimalLambda.OpenTelemetry +dotnet add package OpenTelemetry.Extensions.Hosting +``` + +Trigger envelopes: add exactly the matching package, e.g. + +```bash +dotnet add package MinimalLambda.Envelopes.ApiGateway +dotnet add package MinimalLambda.Envelopes.Sqs +``` + +Keep `MinimalLambda.Testing` version aligned with `MinimalLambda`. + +## Project file basics + +Use C# 11+ because interceptors/source generation paths require modern language support. Repo uses newer versions; client project can use current SDK/LangVersion. + +```xml + + net8.0 + enable + enable + latest + +``` + +For Native AOT, client project also needs normal AWS Lambda AOT settings. Validate with publish, not only build. + +## `Program.cs` template + +```csharp +using MinimalLambda.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = LambdaApplication.CreateBuilder(); + +builder.Services.AddScoped(); + +await using var lambda = builder.Build(); + +lambda.MapHandler(OrderHandlers.HandleAsync); + +await lambda.RunAsync(); + +public sealed record OrderRequest(string OrderId); +public sealed record OrderResponse(string OrderId, bool Accepted); + +internal static class OrderHandlers +{ + public static Task HandleAsync( + [FromEvent] OrderRequest request, + IOrderService orders, + CancellationToken cancellationToken) => + orders.ProcessAsync(request, cancellationToken); +} + +internal interface IOrderService +{ + Task ProcessAsync(OrderRequest request, CancellationToken cancellationToken); +} + +internal sealed class OrderService : IOrderService +{ + public Task ProcessAsync(OrderRequest request, CancellationToken cancellationToken) => + Task.FromResult(new OrderResponse(request.OrderId, Accepted: true)); +} +``` + +## Configuration + +`CreateBuilder()` loads defaults in documented order and binds MinimalLambda settings from `LambdaHost`. + +`appsettings.json`: + +```json +{ + "LambdaHost": { + "InvocationCancellationBuffer": "00:00:05", + "ClearLambdaOutputFormatting": true + } +} +``` + +Environment variable form: + +```bash +LambdaHost__InvocationCancellationBuffer=00:00:05 +``` + +Code override: + +```csharp +builder.Services.ConfigureLambdaHostOptions(options => +{ + options.InvocationCancellationBuffer = TimeSpan.FromSeconds(5); +}); +``` + +## Top-level statements and tests + +For integration tests that use `LambdaApplicationFactory`, make `Program` visible if needed: + +```csharp +public partial class Program; +``` + +Add it at bottom of `Program.cs` in client app if test project cannot access generated top-level `Program` type. + +## AOT serializer context + +For AOT-friendly JSON serialization: + +```csharp +using System.Text.Json.Serialization; + +[JsonSerializable(typeof(OrderRequest))] +[JsonSerializable(typeof(OrderResponse))] +internal partial class SerializerContext : JsonSerializerContext; + +builder.Services.AddLambdaSerializerWithContext(); +``` + +For envelope payloads, also configure envelope options. See `patterns/aot-and-envelopes.md`. + +## Agent checklist for client setup + +1. Identify trigger and packages. +2. Add `MinimalLambda.Builder` using for builder + `[FromEvent]`. +3. Add service registrations before `Build()`. +4. Add middleware before `MapHandler`. +5. Add exactly one handler mapping path. +6. Add serializer context for AOT or explicit serialization requirements. +7. Add integration test using `MinimalLambda.Testing` when pipeline behavior matters. diff --git a/skills/minimal-lambda/references/core-hosting.md b/skills/minimal-lambda/references/core-hosting.md new file mode 100644 index 00000000..0fda0276 --- /dev/null +++ b/skills/minimal-lambda/references/core-hosting.md @@ -0,0 +1,209 @@ +# Core hosting, handlers, DI, lifecycle, middleware + +Read when task touches `LambdaApplication`, `MapHandler`, `[FromEvent]`, DI, lifecycle hooks, middleware, features, configuration, or source-generated handler behavior. + +## Docs to consult + +- `README.md` +- `docs/getting-started/core-concepts.md` +- `docs/guides/handler-registration.md` +- `docs/guides/dependency-injection.md` +- `docs/guides/middleware.md` +- `docs/guides/lifecycle-management.md` +- `docs/guides/configuration.md` +- `src/MinimalLambda/README.md` +- `src/MinimalLambda.Abstractions/README.md` + +## Code to validate + +- `src/MinimalLambda/Builder/LambdaApplication.cs` +- `src/MinimalLambda/Builder/Extensions/BuilderLambdaApplicationExtensions.cs` +- `src/MinimalLambda/Builder/InterceptionTargets/MapHandlerLambdaApplicationExtensions.cs` +- `src/MinimalLambda/Builder/InterceptionTargets/UseMiddlewareLambdaApplicationExtensions.cs` +- `src/MinimalLambda/Builder/InterceptionTargets/OnInitLambdaApplicationExtensions.cs` +- `src/MinimalLambda/Builder/InterceptionTargets/OnShutdownLambdaApplicationExtensions.cs` +- `src/MinimalLambda/Core/Context/LambdaInvocationContext.cs` +- `src/MinimalLambda/Core/Features/*` +- `src/MinimalLambda/Core/Features/FeatureLambdaInvocationContextExtensions.cs` +- `src/MinimalLambda.SourceGenerators/*` + +## Builder shape + +Typical app: + +```csharp +var builder = LambdaApplication.CreateBuilder(); +builder.Services.AddScoped(); + +await using var lambda = builder.Build(); + +lambda.UseMiddleware(async (context, next) => +{ + await next(context); +}); + +lambda.MapHandler(async ([FromEvent] OrderRequest request, IOrderService service, CancellationToken ct) => + await service.ProcessAsync(request, ct)); + +await lambda.RunAsync(); +``` + +`CreateBuilder()` wires standard .NET configuration/logging/DI defaults unless `LambdaApplicationOptions.DisableDefaults = true`. + +Configuration provider order from docs: + +1. `AWS_` env vars +2. `DOTNET_` env vars +3. `appsettings.json` +4. `appsettings.{Environment}.json` +5. user secrets in Development +6. all env vars + +Framework options bind from `LambdaHost` section, not old `AwsLambdaHost`. + +## Handler registration rules + +- `MapHandler` is source-generated/intercepted. Avoid dynamic delegates/reflection workarounds. +- Multiple `MapHandler` calls may exist in code, but only one can execute at runtime. +- Handler with payload: exactly one `[FromEvent]` parameter. +- Handler with no payload: omit event parameter and omit `[FromEvent]`. +- Other parameters can be services, `[FromKeyedServices(...)]`, `ILambdaInvocationContext`, raw AWS `ILambdaContext`, or `CancellationToken`. +- Return values can be `T`, `Task`, `ValueTask`, `Task`, `ValueTask`; serializer/envelope handles response. + +Good handler style: + +```csharp +lambda.MapHandler(MyHandlers.HandleAsync); + +internal static class MyHandlers +{ + public static Task HandleAsync( + [FromEvent] OrderRequest request, + IOrderService service, + CancellationToken ct) => + service.ProcessAsync(request, ct); +} +``` + +Method groups keep handler logic unit-testable. + +## DI and lifetimes + +- Singleton: reused across warm invocations. Good for `HttpClient`, AWS SDK clients, caches, config. +- Scoped: new per invocation. Good default for repositories, DbContexts, per-request state. +- Transient: new per resolve. Good for lightweight helpers. +- Never store scoped service on singleton. +- Prefer constructor/parameter injection over manual `IServiceProvider` resolution. + +## Context and features + +`ILambdaInvocationContext` resembles `HttpContext` for Lambda: + +- `ServiceProvider` scoped to invocation +- `CancellationToken` cancels before hard timeout using configured buffer +- `Items` per-invocation bag +- `Properties` shared cross-invocation dictionary; use thread-safe values +- `Features` typed feature collection +- also exposes AWS Lambda context members + +Useful feature helpers from docs: + +```csharp +if (context.TryGetEvent(out var request)) { } +if (context.TryGetResponse(out var response)) + context.Features.Get>()!.SetResponse(response); +``` + +Use features in middleware to avoid coupling middleware directly to handlers. + +## Middleware + +Register before `MapHandler`. Execution order follows registration order and unwinds in reverse. + +Inline middleware: quick app-specific glue. + +```csharp +lambda.UseMiddleware(async (context, next) => +{ + var logger = context.ServiceProvider.GetRequiredService>(); + logger.LogInformation("Before"); + await next(context); + logger.LogInformation("After"); +}); +``` + +Class middleware: reusable/testable. + +```csharp +internal sealed class LoggingMiddleware(ILogger logger) : ILambdaMiddleware +{ + public async Task InvokeAsync(ILambdaInvocationContext context, LambdaInvocationDelegate next) + { + logger.LogInformation("Invocation starting"); + await next(context); + } +} + +lambda.UseMiddleware(); +``` + +Order guidance: + +- diagnostics first so they wrap all work +- auth before validation/business logic +- short-circuit/caching near handler when it depends on final event/response type + +## Lifecycle + +`OnInit`: + +- cold start once per execution environment +- each handler gets fresh scope +- handlers run concurrently (`Task.WhenAll` per docs) +- `bool`/`Task` can abort startup on `false`; no return implies success +- exceptions aggregate and bubble so container does not serve traffic + +`OnShutdown`: + +- runs once on teardown/SIGTERM +- fresh scope per handler +- bounded by `ShutdownDuration - ShutdownDurationBuffer` +- use to flush telemetry/dispose external resources + +Example: + +```csharp +lambda.OnInit(async (ICache cache, CancellationToken ct) => +{ + await cache.WarmAsync(ct); + return true; +}); + +lambda.OnShutdown(async (ITelemetrySink sink, CancellationToken ct) => +{ + await sink.FlushAsync(ct); +}); +``` + +## Options + +Use `builder.Services.ConfigureLambdaHostOptions(options => { ... })`. + +Important options: + +- `InitTimeout` default 5s +- `InvocationCancellationBuffer` default 500ms +- `ShutdownDuration` default external extension window (500ms) +- `ShutdownDurationBuffer` default 50ms +- `ClearLambdaOutputFormatting` +- `BootstrapHttpClient` +- `BootstrapOptions` + +## Common pitfalls + +- Missing `[FromEvent]` for payload handler → generator diagnostic. +- Duplicate `[FromEvent]` → generator diagnostic. +- Registering middleware after `MapHandler` likely means it will not wrap as intended. +- Multiple `MapHandler` runtime calls → `InvalidOperationException`. +- Manual service resolution everywhere → less testable; prefer injected params. +- Ignoring cancellation token → bad Lambda timeout behavior. diff --git a/skills/minimal-lambda/references/envelopes.md b/skills/minimal-lambda/references/envelopes.md new file mode 100644 index 00000000..7e1b90e2 --- /dev/null +++ b/skills/minimal-lambda/references/envelopes.md @@ -0,0 +1,102 @@ +# Envelopes + +Read when task touches SQS, SNS, API Gateway, Kinesis, Kinesis Firehose, Kafka/MSK, CloudWatch Logs, ALB, event bodies, typed payloads, or AWS trigger-specific request/response types. + +## Docs to consult + +- `docs/features/envelopes.md` +- `src/Envelopes/README.md` +- `src/Envelopes/MinimalLambda.Envelopes/README.md` +- trigger package README: + - `src/Envelopes/MinimalLambda.Envelopes.Sqs/README.md` + - `src/Envelopes/MinimalLambda.Envelopes.Sns/README.md` + - `src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md` + - `src/Envelopes/MinimalLambda.Envelopes.Kinesis/README.md` + - `src/Envelopes/MinimalLambda.Envelopes.KinesisFirehose/README.md` + - `src/Envelopes/MinimalLambda.Envelopes.Kafka/README.md` + - `src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs/README.md` + - `src/Envelopes/MinimalLambda.Envelopes.Alb/README.md` + +## Code to validate + +- `src/Envelopes/MinimalLambda.Envelopes*/**/*.cs` +- `tests/MinimalLambda.Envelopes.UnitTests/` +- `examples/MinimalLambda.Example.Events/` + +## Mental model + +Envelope packages wrap official AWS Lambda event classes and add type-safe payload access, commonly `BodyContent`, so client code avoids manual JSON parsing of strings. + +Benefits: + +- typed payload contracts +- trigger-specific envelope support +- AOT-friendly serializer context paths +- reuse AWS event shape while adding generic body content + +## Package selection + +Use only package(s) matching trigger: + +| Trigger | Package | +| ------------------------------- | ------------------------------------------------- | +| SQS | `MinimalLambda.Envelopes.Sqs` | +| SNS | `MinimalLambda.Envelopes.Sns` | +| SNS-to-SQS | `MinimalLambda.Envelopes.Sqs` SNS-to-SQS envelope | +| API Gateway REST/HTTP/WebSocket | `MinimalLambda.Envelopes.ApiGateway` | +| Kinesis Data Streams | `MinimalLambda.Envelopes.Kinesis` | +| Kinesis Firehose transform | `MinimalLambda.Envelopes.KinesisFirehose` | +| Kafka/MSK/self-managed | `MinimalLambda.Envelopes.Kafka` | +| CloudWatch Logs | `MinimalLambda.Envelopes.CloudWatchLogs` | +| ALB | `MinimalLambda.Envelopes.Alb` | + +## Exact common types + +| Trigger | Request/event type | Response type | +| -------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------- | +| API Gateway REST/HTTP v1/WebSocket | `ApiGatewayRequestEnvelope` | `ApiGatewayResponseEnvelope` or `ApiGatewayResult` | +| API Gateway HTTP API v2 / Function URL | `ApiGatewayV2RequestEnvelope` | `ApiGatewayV2ResponseEnvelope` or `ApiGatewayV2Result` | +| ALB | `AlbRequestEnvelope` | `AlbResponseEnvelope` or `AlbResult` | +| SQS | `SqsEnvelope` | usually none | +| SNS | `SnsEnvelope` | usually none | +| SNS-to-SQS | `SqsSnsEnvelope` | usually none | +| Kinesis Data Streams | `KinesisEnvelope` | usually none | +| Kinesis Firehose transform | `KinesisFirehoseEventEnvelope` | `KinesisFirehoseResponseEnvelope` | +| Kafka/MSK/self-managed | `KafkaEnvelope` | usually none | +| CloudWatch Logs | `CloudWatchLogsEnvelope` or `CloudWatchLogsEnvelope` | usually none | + +## Handler shape + +Envelope types still enter through `[FromEvent]`: + +```csharp +lambda.MapHandler(async ([FromEvent] SqsEnvelope envelope, IOrderService service, CancellationToken ct) => +{ + foreach (var message in envelope.Records) + { + if (message.BodyContent is not null) + await service.ProcessAsync(message.BodyContent, ct); + } +}); +``` + +When exact type/member names matter, inspect target package README and source. Names vary by trigger. + +## AOT / serialization guidance + +- Prefer explicit records/classes with predictable JSON contracts. +- For Native AOT, add envelope and payload/response types to `JsonSerializerContext`. +- Register `builder.Services.AddLambdaSerializerWithContext()`. +- For nested envelope payloads, also call `builder.Services.ConfigureEnvelopeOptions(options => options.JsonOptions.TypeInfoResolver = SerializerContext.Default)`. +- Avoid ad-hoc `JsonSerializer.Deserialize` and reflection-based polymorphism. +- Match request and response envelope types for API Gateway/ALB-style triggers. + +See `patterns/aot-and-envelopes.md` for complete snippets. + +## Agent workflow for envelope questions + +1. Identify AWS event source. +2. Read matching envelope README. +3. Inspect source/tests for exact type/member names. +4. Propose minimal package references and handler signature. +5. Add/adjust tests using `MinimalLambda.Testing` or existing envelope unit test style. diff --git a/skills/minimal-lambda/references/opentelemetry.md b/skills/minimal-lambda/references/opentelemetry.md new file mode 100644 index 00000000..89fa5004 --- /dev/null +++ b/skills/minimal-lambda/references/opentelemetry.md @@ -0,0 +1,85 @@ +# OpenTelemetry + +Read when task asks for tracing, metrics, X-Ray/OTLP, `UseOpenTelemetryTracing`, AWS Lambda instrumentation, or telemetry flush on shutdown. + +## Docs to consult + +- `docs/features/open_telemetry.md` +- `src/MinimalLambda.OpenTelemetry/README.md` +- `examples/MinimalLambda.Example.OpenTelemetry/README.md` +- `src/MinimalLambda.OpenTelemetry/` +- `tests/MinimalLambda.OpenTelemetry.UnitTests/` + +## Packages + +Typical packages: + +```bash +dotnet add package MinimalLambda.OpenTelemetry +dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol +dotnet add package OpenTelemetry.Extensions.Hosting +``` + +X-Ray often also needs: + +```bash +dotnet add package OpenTelemetry.Contrib.Extensions.AWSXRay +``` + +## Basic setup + +```csharp +var builder = LambdaApplication.CreateBuilder(); + +builder.Services + .AddOpenTelemetry() + .WithTracing(tracing => + { + tracing.AddAWSLambdaConfigurations(); + tracing.AddSource("MyService"); + tracing.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyService")); + tracing.AddOtlpExporter(); + }) + .WithMetrics(metrics => + { + metrics.AddMeter("MyService"); + metrics.AddOtlpExporter(); + }); + +await using var lambda = builder.Build(); + +lambda.UseOpenTelemetryTracing(); +lambda.OnShutdownFlushOpenTelemetry(); + +lambda.MapHandler(async ([FromEvent] Request request, ILogger logger, CancellationToken ct) => +{ + logger.LogInformation("Handling {Name}", request.Name); + return new Response($"Hello {request.Name}"); +}); + +await lambda.RunAsync(); +``` + +## How it works + +`UseOpenTelemetryTracing()` adds invocation middleware. It reads event and response through feature collection and delegates root-span creation/context propagation to official `OpenTelemetry.Instrumentation.AWSLambda`. + +`OnShutdownFlushOpenTelemetry()` registers shutdown hook to force-flush tracer and meter providers before Lambda freezes/terminates environment. + +## Rules and pitfalls + +- Configure OpenTelemetry services before `builder.Build()`. +- Call `lambda.UseOpenTelemetryTracing()` before `MapHandler` so tracing wraps handler. +- Register `TracerProvider`; startup fails if required provider missing. +- Add custom `ActivitySource` names via `AddSource` and custom `Meter` names via `AddMeter`. +- Use shutdown flush for buffered exporters. +- Keep exporter config Lambda-safe; avoid long flush windows beyond shutdown budget. + +## Agent workflow + +1. Identify backend: OTLP, X-Ray, console, vendor exporter. +2. Add required NuGet packages. +3. Configure `AddOpenTelemetry()` with tracing/metrics. +4. Add `UseOpenTelemetryTracing()` and shutdown flush. +5. Validate source names/meters match app instrumentation. +6. For tests, inspect OpenTelemetry unit tests for exact assertions/patterns. diff --git a/skills/minimal-lambda/references/patterns/aot-and-envelopes.md b/skills/minimal-lambda/references/patterns/aot-and-envelopes.md new file mode 100644 index 00000000..c5925529 --- /dev/null +++ b/skills/minimal-lambda/references/patterns/aot-and-envelopes.md @@ -0,0 +1,73 @@ +# AOT and serializer patterns + +Read when user targets Native AOT, trimming, or envelope body deserialization. + +## Plain event/response context + +```csharp +using System.Text.Json.Serialization; + +[JsonSerializable(typeof(OrderRequest))] +[JsonSerializable(typeof(OrderResponse))] +internal partial class SerializerContext : JsonSerializerContext; + +builder.Services.AddLambdaSerializerWithContext(); +``` + +`AddLambdaSerializerWithContext()` registers AWS `ILambdaSerializer` backed by source-generated metadata. + +## Envelope context + +Envelope packages deserialize in two steps: + +1. Lambda serializer deserializes raw AWS event/envelope. +2. Envelope code deserializes nested body/message/record payload. + +So register both Lambda serializer and envelope options. + +```csharp +using System.Text.Json.Serialization; +using MinimalLambda.Envelopes.ApiGateway; + +[JsonSerializable(typeof(ApiGatewayRequestEnvelope))] +[JsonSerializable(typeof(ApiGatewayResponseEnvelope))] +[JsonSerializable(typeof(CreateOrderRequest))] +[JsonSerializable(typeof(CreateOrderResponse))] +internal partial class SerializerContext : JsonSerializerContext; + +builder.Services.AddLambdaSerializerWithContext(); + +builder.Services.ConfigureEnvelopeOptions(options => +{ + options.JsonOptions.TypeInfoResolver = SerializerContext.Default; +}); +``` + +## Kinesis example + +Docs show this pattern: + +```csharp +[JsonSerializable(typeof(KinesisEnvelope))] +[JsonSerializable(typeof(StreamRecord))] +internal partial class SerializerContext : JsonSerializerContext; + +builder.Services.AddLambdaSerializerWithContext(); + +builder.Services.ConfigureEnvelopeOptions(options => +{ + options.JsonOptions.TypeInfoResolver = SerializerContext.Default; +}); +``` + +## AOT review checklist + +- [ ] Every event/envelope/response/payload type appears in `JsonSerializerContext`. +- [ ] `AddLambdaSerializerWithContext()` is registered before `Build()`. +- [ ] Envelope payloads also configure `ConfigureEnvelopeOptions`. +- [ ] No runtime reflection over handlers/contracts. +- [ ] Publish has been tested; build alone is not enough. + +## When not to overdo it + +If client project is not AOT/trimming-sensitive, normal `System.Text.Json` fallback may be acceptable. Still prefer explicit contracts and avoid dynamic serialization for Lambda cold-start performance and reliability. diff --git a/skills/minimal-lambda/references/patterns/envelope-patterns.md b/skills/minimal-lambda/references/patterns/envelope-patterns.md new file mode 100644 index 00000000..2e0e7b2c --- /dev/null +++ b/skills/minimal-lambda/references/patterns/envelope-patterns.md @@ -0,0 +1,98 @@ +# Envelope patterns + +Read when selecting or implementing strongly typed AWS trigger envelopes. + +## Exact envelope types + +| Trigger | Request/event type | Response type | +| -------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------- | +| API Gateway REST/HTTP v1/WebSocket | `ApiGatewayRequestEnvelope` | `ApiGatewayResponseEnvelope` or `ApiGatewayResult` | +| API Gateway HTTP API v2 / Function URL | `ApiGatewayV2RequestEnvelope` | `ApiGatewayV2ResponseEnvelope` or `ApiGatewayV2Result` | +| ALB | `AlbRequestEnvelope` | `AlbResponseEnvelope` or `AlbResult` | +| SQS | `SqsEnvelope` | usually no response | +| SNS | `SnsEnvelope` | usually no response | +| SNS-to-SQS | `SqsSnsEnvelope` | usually no response | +| Kinesis Data Streams | `KinesisEnvelope` | usually no response | +| Kinesis Firehose transform | `KinesisFirehoseEventEnvelope` | `KinesisFirehoseResponseEnvelope` | +| Kafka/MSK | `KafkaEnvelope` | usually no response | +| CloudWatch Logs | `CloudWatchLogsEnvelope` or `CloudWatchLogsEnvelope` | usually no response | + +Always inspect matching README/source for current property names and special cases. + +## API Gateway result pattern + +Use result builders when handler can return different response body types. + +```csharp +lambda.MapHandler(([FromEvent] ApiGatewayRequestEnvelope request) => +{ + if (request.BodyContent is null) + return ApiGatewayResult.BadRequest(new ErrorResponse("Missing body")); + + return ApiGatewayResult.Created(new CreateOrderResponse(request.BodyContent.OrderId)); +}); +``` + +Use envelope response when response type is stable and you need full control. + +```csharp +lambda.MapHandler(([FromEvent] ApiGatewayRequestEnvelope request) => + new ApiGatewayResponseEnvelope + { + StatusCode = 200, + BodyContent = new CreateOrderResponse(request.BodyContent!.OrderId), + Headers = new Dictionary { ["Content-Type"] = "application/json" }, + }); +``` + +## SQS batch pattern + +```csharp +lambda.MapHandler(async ([FromEvent] SqsEnvelope envelope, IOrderService orders, CancellationToken ct) => +{ + foreach (var message in envelope.Records) + { + if (message.BodyContent is null) + continue; + + await orders.ProcessAsync(message.BodyContent, ct); + } +}); +``` + +Production code often needs partial-batch failure support depending on AWS integration. Check package/docs/current support before promising behavior. + +## Kinesis pattern + +```csharp +lambda.MapHandler(async ([FromEvent] KinesisEnvelope envelope, IStreamProcessor processor, CancellationToken ct) => +{ + foreach (var record in envelope.Records) + { + if (record.Kinesis.DataContent is not null) + await processor.ProcessAsync(record.Kinesis.DataContent, ct); + } +}); +``` + +Kinesis payload appears on `record.Kinesis.DataContent`. + +## Firehose transform pattern + +```csharp +lambda.MapHandler(([FromEvent] KinesisFirehoseEventEnvelope envelope) => +{ + var response = new KinesisFirehoseResponseEnvelope(); + + // Inspect package README/source for current record-construction API. + // Preserve record IDs and set transform result per AWS Firehose contract. + + return response; +}); +``` + +Firehose response contracts are easy to get wrong; validate against package tests/source. + +## AOT envelope pattern + +See `aot-and-envelopes.md`. Register both Lambda serializer and envelope options because raw event and nested body content deserialize at different layers. diff --git a/skills/minimal-lambda/references/patterns/handler-patterns.md b/skills/minimal-lambda/references/patterns/handler-patterns.md new file mode 100644 index 00000000..7122795b --- /dev/null +++ b/skills/minimal-lambda/references/patterns/handler-patterns.md @@ -0,0 +1,97 @@ +# Handler patterns + +Read when implementing or reviewing handler shape. + +## Thin method-group handler + +Best default for client projects. + +```csharp +lambda.MapHandler(OrderHandlers.HandleAsync); + +internal static class OrderHandlers +{ + public static Task HandleAsync( + [FromEvent] OrderRequest request, + IOrderService orders, + CancellationToken ct) => + orders.ProcessAsync(request, ct); +} +``` + +Why: + +- source generator gets explicit signature +- handler can be unit-tested directly +- business logic stays in service + +## No-event handler + +Use for scheduled/heartbeat style Lambda where payload not needed. + +```csharp +lambda.MapHandler(async (IJobRunner jobs, CancellationToken ct) => +{ + await jobs.RunAsync(ct); +}); +``` + +No `[FromEvent]`; no fake unused event parameter. + +## Context-aware handler + +Use `ILambdaInvocationContext` when handler needs AWS request metadata, per-invocation bag, or features. + +```csharp +lambda.MapHandler(async ( + [FromEvent] OrderRequest request, + ILambdaInvocationContext context, + IOrderService orders, + CancellationToken ct) => +{ + context.Items["OrderId"] = request.OrderId; + return await orders.ProcessAsync(request, context.AwsRequestId, ct); +}); +``` + +Use context sparingly. Prefer services for business operations. + +## Keyed service handler + +Use .NET keyed services for explicit variant selection. + +```csharp +builder.Services.AddKeyedScoped("primary"); + +lambda.MapHandler(( + [FromEvent] OrderRequest request, + [FromKeyedServices("primary")] IOrderProcessor processor, + CancellationToken ct) => + processor.ProcessAsync(request, ct)); +``` + +Keep keys simple constants. + +## Unit-testable handler method + +```csharp +[Fact] +public async Task HandleAsync_ReturnsAcceptedOrder() +{ + var orders = Substitute.For(); + var request = new OrderRequest("order-123"); + var expected = new OrderResponse("order-123", Accepted: true); + + orders.ProcessAsync(request, Arg.Any()).Returns(expected); + + var actual = await OrderHandlers.HandleAsync(request, orders, TestContext.Current.CancellationToken); + + actual.Should().Be(expected); +} +``` + +Use integration tests for source-generated binding; direct unit tests for business behavior. + +## Anti-pattern: routing many event shapes in one handler + +Avoid big `object`/JSON switch dispatch when separate Lambda functions or explicit envelope types fit. It hides contracts from source generation, tests, and AOT serializer metadata. diff --git a/skills/minimal-lambda/references/patterns/middleware-patterns.md b/skills/minimal-lambda/references/patterns/middleware-patterns.md new file mode 100644 index 00000000..f43b00e8 --- /dev/null +++ b/skills/minimal-lambda/references/patterns/middleware-patterns.md @@ -0,0 +1,117 @@ +# Middleware patterns + +Read when adding logging, metrics, validation, auth, idempotency, response mapping, or feature access. + +## Inline logging/correlation + +```csharp +lambda.UseMiddleware(async (context, next) => +{ + var logger = context.ServiceProvider.GetRequiredService>(); + var correlationId = context.AwsRequestId; + + using var scope = logger.BeginScope(new Dictionary + { + ["AwsRequestId"] = correlationId, + }); + + context.Items["CorrelationId"] = correlationId; + + await next(context); +}); +``` + +Good for app-local glue. Keep heavy logic in services. + +## Feature-based validation + +```csharp +lambda.UseMiddleware(async (context, next) => +{ + if (!context.TryGetEvent(out var request)) + { + await next(context); + return; + } + + if (string.IsNullOrWhiteSpace(request.OrderId)) + { + context.Features.Get>()! + .SetResponse(new OrderResponse("", Accepted: false)); + return; + } + + await next(context); +}); +``` + +Features let middleware work with typed event/response without coupling to handler implementation. + +## Class-based middleware + +```csharp +internal sealed class TimingMiddleware(ILogger logger) : ILambdaMiddleware +{ + public async Task InvokeAsync(ILambdaInvocationContext context, LambdaInvocationDelegate next) + { + var started = Stopwatch.GetTimestamp(); + + try + { + await next(context); + } + finally + { + var elapsed = Stopwatch.GetElapsedTime(started); + logger.LogInformation("Invocation completed in {ElapsedMs} ms", elapsed.TotalMilliseconds); + } + } +} + +lambda.UseMiddleware(); +``` + +Use class middleware for reusable code, dependencies, and easier unit tests. + +## Short-circuit cache + +```csharp +lambda.UseMiddleware(async (context, next) => +{ + var cache = context.ServiceProvider.GetRequiredService(); + + if (context.TryGetEvent(out var request) + && await cache.TryGetAsync(request.OrderId, context.CancellationToken) is { } cached) + { + context.Features.Get>()!.SetResponse(cached); + return; + } + + await next(context); + + if (request is not null && context.GetResponse() is { } response) + await cache.SetAsync(request.OrderId, response, context.CancellationToken); +}); +``` + +Place short-circuit middleware after auth/validation and before handler. + +## Error boundary pattern + +```csharp +lambda.UseMiddleware(async (context, next) => +{ + try + { + await next(context); + } + catch (ValidationException ex) + { + var logger = context.ServiceProvider.GetRequiredService>(); + logger.LogWarning(ex, "Validation failed"); + context.Features.Get>()!.SetResponse(new ErrorResponse(ex.Message)); + } +}); +``` + +Use trigger-specific HTTP result/envelope for API Gateway/ALB when mapping errors to status codes. diff --git a/skills/minimal-lambda/references/patterns/testing-patterns.md b/skills/minimal-lambda/references/patterns/testing-patterns.md new file mode 100644 index 00000000..08785a28 --- /dev/null +++ b/skills/minimal-lambda/references/patterns/testing-patterns.md @@ -0,0 +1,93 @@ +# Testing patterns + +Read when adding client-project tests with `MinimalLambda.Testing`. + +## End-to-end happy path + +```csharp +public sealed class OrderLambdaTests +{ + [Fact] + public async Task InvokeAsync_ReturnsAcceptedOrder() + { + await using var factory = new LambdaApplicationFactory() + .WithCancellationToken(TestContext.Current.CancellationToken); + + var response = await factory.TestServer.InvokeAsync( + new OrderRequest("order-123"), + TestContext.Current.CancellationToken); + + response.WasSuccess.Should().BeTrue(); + response.Response.Should().Be(new OrderResponse("order-123", Accepted: true)); + } +} +``` + +`InvokeAsync` starts host on demand. Call `StartAsync` explicitly when init status matters. + +## Assert startup/init behavior + +```csharp +await using var factory = new LambdaApplicationFactory() + .WithCancellationToken(TestContext.Current.CancellationToken); + +var init = await factory.TestServer.StartAsync(TestContext.Current.CancellationToken); + +init.InitStatus.Should().Be(InitStatus.InitCompleted); +``` + +Use fresh factory per test when checking lifecycle hooks. + +## Override services + +```csharp +await using var factory = new LambdaApplicationFactory() + .WithHostBuilder(builder => + { + builder.ConfigureServices((_, services) => + { + services.RemoveAll(); + services.AddScoped(_ => Substitute.For()); + }); + }); +``` + +Use this for external dependencies. Do not mock MinimalLambda runtime when runtime behavior is under test. + +## No-event handler + +```csharp +var response = await factory.TestServer.InvokeNoEventAsync( + TestContext.Current.CancellationToken); + +response.WasSuccess.Should().BeTrue(); +``` + +## No-response handler + +```csharp +var response = await factory.TestServer.InvokeNoResponseAsync( + new JobRequest("sync"), + TestContext.Current.CancellationToken); + +response.WasSuccess.Should().BeTrue(); +``` + +## Error assertion + +```csharp +var response = await factory.TestServer.InvokeAsync( + new OrderRequest("bad"), + TestContext.Current.CancellationToken); + +response.WasSuccess.Should().BeFalse(); +response.Error.Should().NotBeNull(); +``` + +Assert structured Lambda-style error payload, not local exception type, for invocation failures. + +## Shared factory fixture + +Use `IClassFixture>` for speed only when shared singletons and one-time `OnInit` are acceptable. + +Avoid shared factory when tests mutate configuration, singleton state, or lifecycle assertions. diff --git a/skills/minimal-lambda/references/repo-workflow.md b/skills/minimal-lambda/references/repo-workflow.md new file mode 100644 index 00000000..71a55805 --- /dev/null +++ b/skills/minimal-lambda/references/repo-workflow.md @@ -0,0 +1,95 @@ +# MinimalLambda repo workflow and implementation notes + +Read when changing MinimalLambda itself, source generators, packages, docs, examples, AOT compatibility, or tests. + +## Repo guardrails + +Follow root `AGENTS.md`: + +- small focused diffs +- match existing patterns +- run formatting + tests before handoff when practical +- avoid reflection-heavy/dynamic code unless required and guarded +- Lambda-first and AOT-friendly + +## Commands + +Restore: + +```bash +DOTNET_NOLOGO=1 dotnet restore +DOTNET_NOLOGO=1 dotnet tool restore +``` + +Build: + +```bash +DOTNET_NOLOGO=1 dotnet build --configuration Release --no-restore /p:TreatWarningsAsErrors=true +``` + +Tests: + +```bash +task test:all +# or +DOTNET_NOLOGO=1 dotnet test --configuration Release -f net10.0 +``` + +AOT check: + +```bash +DOTNET_NOLOGO=1 dotnet publish src/AotCompatibility.TestApp/AotCompatibility.TestApp.csproj /p:TreatWarningsAsErrors=true +``` + +Format: + +```bash +task format +# or task format:csharpier for C# formatting only +``` + +## Code style + +- nullable enabled; treat nullability warnings as bugs +- C# preview/C# 14 features present +- extension blocks `extension(...) { ... }` are intentional; do not rewrite to old extension syntax +- file-scoped namespaces +- `sealed` for public classes unless inheritance intended +- `internal` for implementation details +- prefer `ArgumentNullException.ThrowIfNull(arg)` +- avoid dynamic/reflection on hot paths and source-generated/AOT paths + +## Source generator landmarks + +- entry: `src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs` +- syntax providers: `SyntaxProviders/` +- models: `Models/Handlers/`, `Models/Middleware/` +- diagnostics: `Diagnostics/` +- emitters: `Emitters/` +- templates: search for `.scriban` +- tests/snapshots: `tests/MinimalLambda.SourceGenerators.UnitTests/` + +Generator responsibilities include: + +- intercepting `MapHandler`, lifecycle hooks, class middleware registration +- validating `[FromEvent]` count and keyed service metadata +- generating reflection-free invocation glue + +## Runtime landmarks + +- `src/MinimalLambda/Builder/` app builder, invocation/lifecycle builders, extension targets +- `src/MinimalLambda/Core/Context/` invocation/lifecycle contexts +- `src/MinimalLambda/Core/Features/` event/response/features +- `src/MinimalLambda/Runtime/` hosted service/bootstrap integration +- `src/MinimalLambda.Abstractions/` public contracts + +## Test strategy + +- Unit tests for isolated runtime/generator behavior. +- Snapshot tests for generated code changes; update snapshots only when intended. +- Integration tests via `MinimalLambda.Testing` for pipeline behavior. +- AOT test app for trimming/native publish compatibility. + +## Before final handoff + +Report commands run and results. If not run, say why. diff --git a/skills/minimal-lambda/references/testing.md b/skills/minimal-lambda/references/testing.md new file mode 100644 index 00000000..aa4cee29 --- /dev/null +++ b/skills/minimal-lambda/references/testing.md @@ -0,0 +1,98 @@ +# Testing client projects with MinimalLambda.Testing + +Read when task asks for integration tests, in-memory Lambda execution, test fixtures, host overrides, lifecycle tests, or client project test setup. + +## Docs to consult + +- `docs/guides/testing.md` +- `src/MinimalLambda.Testing/README.md` +- `tests/MinimalLambda.Testing.UnitTests/` + +## Core idea + +`MinimalLambda.Testing` behaves like ASP.NET Core `WebApplicationFactory`: boot real Lambda entry point in memory and speak same Runtime API contract as AWS. + +Use for: + +- source-generated handler coverage +- middleware/envelope/DI/lifecycle integration +- host customization in tests +- regression tests for error payloads and cold-start behavior + +Prefer plain unit tests for isolated business logic. + +## Package rule + +`MinimalLambda.Testing` version should match `MinimalLambda` version. + +## Basic xUnit shape + +```csharp +await using var factory = new LambdaApplicationFactory() + .WithCancellationToken(TestContext.Current.CancellationToken); + +var initResult = await factory.TestServer.StartAsync(TestContext.Current.CancellationToken); +initResult.InitStatus.Should().Be(InitStatus.InitCompleted); + +var response = await factory.TestServer.InvokeAsync( + new MyEvent("World"), + TestContext.Current.CancellationToken); + +response.WasSuccess.Should().BeTrue(); +response.Response.Message.Should().Be("Hello World!"); +``` + +## Invocation APIs + +- `InvokeAsync(event, token)` for typed input + typed output. +- `InvokeNoEventAsync(token)` for no event payload. +- `InvokeNoResponseAsync(event, token)` for no response body. + +Responses expose: + +- `WasSuccess` +- `Response` +- `Error` for structured Lambda-style failure payload + +## Host customization + +Use `WithHostBuilder` for test-only config/services. + +```csharp +await using var factory = new LambdaApplicationFactory() + .WithHostBuilder(builder => + { + builder.ConfigureServices((_, services) => + { + services.RemoveAll(); + services.AddScoped(); + }); + }); +``` + +Also supports app configuration overrides and custom service provider factories. + +## Shared fixtures caution + +Using one factory across many tests improves speed but shares: + +- `OnInit` once +- `OnShutdown` once at fixture disposal +- singleton services across tests + +Do not share factory when testing init/shutdown behavior or singleton isolation. + +## Cancellation/timeouts + +- Use `WithCancellationToken` to flow test cancellation. +- Per-call tokens bound individual invokes. +- `ServerOptions.FunctionTimeout` defaults to 3 seconds; adjust to test timeout behavior. + +## Agent workflow + +1. Check client target framework/test framework. +2. Add matching `MinimalLambda.Testing` package. +3. Ensure `Program` accessible to test project if needed (`public partial class Program` pattern if client uses top-level statements). +4. Pick right invoke API. +5. Assert `WasSuccess` before accessing response. +6. Test error cases through `Error`, not raw exceptions unless host startup fails. diff --git a/skills/minimal-lambda/references/troubleshooting.md b/skills/minimal-lambda/references/troubleshooting.md new file mode 100644 index 00000000..d0d4eb1f --- /dev/null +++ b/skills/minimal-lambda/references/troubleshooting.md @@ -0,0 +1,106 @@ +# Troubleshooting MinimalLambda usage + +Read when user reports compile errors, source generator diagnostics, runtime startup failures, handler not running, serialization issues, middleware not firing, or test failures. + +## Source generator / compile-time issues + +### `MapHandler` call not intercepted + +Symptoms: + +- runtime exception: `This method is replaced at compile time.` +- generated handler missing + +Check: + +- project uses supported C# language version +- package references include `MinimalLambda` +- `MapHandler` call shape is a static, analyzable delegate/method group +- source generator diagnostics in build output + +### Missing or duplicate `[FromEvent]` + +Payload handler needs exactly one `[FromEvent]` parameter. No-payload handler should have no event parameter. + +Bad: + +```csharp +lambda.MapHandler((OrderRequest request, IOrderService service) => service.Process(request)); +``` + +Good: + +```csharp +lambda.MapHandler(([FromEvent] OrderRequest request, IOrderService service) => service.Process(request)); +``` + +### Keyed service diagnostic + +`[FromKeyedServices(...)]` keys must be supported constants. If generator reports unsupported metadata, replace complex key object with string/int/enum-style supported key. + +## Runtime issues + +### Multiple handlers registered + +Only one handler can be registered per Lambda execution. Conditional mapping is fine; executing multiple mappings is not. + +### DI service missing + +Handler parameters not marked `[FromEvent]` resolve from DI/context. If a custom service parameter fails, register it before `builder.Build()`. + +### Scoped service leak + +Warm Lambda containers reuse singletons. If per-invocation state appears in later invocations, check singleton fields and static state for captured scoped services/data. + +### Middleware not running + +Check registration order. Middleware should be registered before `MapHandler`. + +## Serialization/envelope issues + +### Body content null + +For API Gateway/ALB/SQS/etc. envelope types, inspect raw event shape and matching package README. Common causes: + +- wrong envelope type for trigger version +- request body absent or not JSON +- AOT serializer context missing payload/envelope type +- custom content type unsupported by default JSON envelope + +### Native AOT serialization failure + +Add all event, envelope, payload, and response types to `JsonSerializerContext`. Register both Lambda serializer and envelope options when envelopes deserialize nested payloads. + +```csharp +builder.Services.AddLambdaSerializerWithContext(); +builder.Services.ConfigureEnvelopeOptions(options => +{ + options.JsonOptions.TypeInfoResolver = SerializerContext.Default; +}); +``` + +## Testing issues + +### `LambdaApplicationFactory` cannot access `Program` + +Add public partial class marker at bottom of top-level `Program.cs`: + +```csharp +public partial class Program; +``` + +### Test passes alone, fails in class fixture + +Shared factory reuses host/singletons and runs `OnInit` once. Use fresh factory when test needs isolation. + +### Invocation times out + +Check `factory.ServerOptions.FunctionTimeout`, long-running middleware, and cancellation token propagation. + +## Debug workflow + +1. Reproduce with `dotnet build` first for generator diagnostics. +2. Run a focused integration test with `MinimalLambda.Testing`. +3. Log event/response feature types in middleware if binding unclear. +4. Compare handler/envelope type with matching package README and source. +5. For AOT, publish the app; normal build is insufficient. diff --git a/skills/minimal-lambda/scripts/validate_references.py b/skills/minimal-lambda/scripts/validate_references.py new file mode 100755 index 00000000..108395bf --- /dev/null +++ b/skills/minimal-lambda/scripts/validate_references.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Validate MinimalLambda skill references against repo layout. + +Run from repo root: + python skills/minimal-lambda/scripts/validate_references.py +""" + +from __future__ import annotations + +from pathlib import Path +import re +import sys + +ROOT = Path.cwd() +SKILL = ROOT / "skills" / "minimal-lambda" + +REQUIRED_FILES = [ + SKILL / "SKILL.md", + SKILL / "references" / "core-hosting.md", + SKILL / "references" / "best-practices.md", + SKILL / "references" / "client-project-setup.md", + SKILL / "references" / "envelopes.md", + SKILL / "references" / "testing.md", + SKILL / "references" / "opentelemetry.md", + SKILL / "references" / "troubleshooting.md", + SKILL / "references" / "repo-workflow.md", + SKILL / "references" / "patterns" / "handler-patterns.md", + SKILL / "references" / "patterns" / "middleware-patterns.md", + SKILL / "references" / "patterns" / "envelope-patterns.md", + SKILL / "references" / "patterns" / "testing-patterns.md", + SKILL / "references" / "patterns" / "aot-and-envelopes.md", + SKILL / "evals" / "evals.json", +] + +REPO_PATHS = [ + "README.md", + "docs/getting-started/core-concepts.md", + "docs/guides/handler-registration.md", + "docs/guides/dependency-injection.md", + "docs/guides/middleware.md", + "docs/guides/lifecycle-management.md", + "docs/guides/configuration.md", + "docs/guides/testing.md", + "docs/features/envelopes.md", + "docs/features/open_telemetry.md", + "src/MinimalLambda/Builder/LambdaApplication.cs", + "src/MinimalLambda/Builder/Extensions/BuilderLambdaApplicationExtensions.cs", + "src/MinimalLambda/Builder/InterceptionTargets/MapHandlerLambdaApplicationExtensions.cs", + "src/MinimalLambda/Core/Features/FeatureLambdaInvocationContextExtensions.cs", + "src/MinimalLambda.Testing/README.md", + "src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md", +] + +SYMBOL_CHECKS = { + "LambdaApplication": "src/MinimalLambda/Builder/LambdaApplication.cs", + "MapHandler": "src/MinimalLambda/Builder/InterceptionTargets/MapHandlerLambdaApplicationExtensions.cs", + "FromEventAttribute": "src/MinimalLambda.Abstractions/Attributes/FromEventAttribute.cs", + "AddLambdaSerializerWithContext": "src/MinimalLambda/Builder/Extensions/SerializerServiceCollectionExtensions.cs", + "ConfigureEnvelopeOptions": "src/MinimalLambda/Builder/Extensions/ConfigurationServiceCollectionExtensions.cs", + "LambdaApplicationFactory": "src/MinimalLambda.Testing/LambdaApplicationFactory.cs", + "ApiGatewayV2RequestEnvelope": "src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2RequestEnvelope.cs", + "SqsEnvelope": "src/Envelopes/MinimalLambda.Envelopes.Sqs/SqsEnvelope.cs", + "KinesisEnvelope": "src/Envelopes/MinimalLambda.Envelopes.Kinesis/KinesisEnvelope.cs", +} + + +def fail(message: str) -> None: + print(f"FAIL: {message}") + sys.exit(1) + + +def main() -> None: + missing = [path for path in REQUIRED_FILES if not path.exists()] + if missing: + fail("missing skill files:\n" + "\n".join(str(p) for p in missing)) + + missing_repo = [ROOT / p for p in REPO_PATHS if not (ROOT / p).exists()] + if missing_repo: + fail("missing repo source/doc paths:\n" + "\n".join(str(p) for p in missing_repo)) + + for symbol, rel_path in SYMBOL_CHECKS.items(): + path = ROOT / rel_path + if not path.exists(): + fail(f"symbol source path missing for {symbol}: {rel_path}") + if symbol not in path.read_text(encoding="utf-8"): + fail(f"symbol {symbol} not found in {rel_path}") + + skill_text = "\n".join(p.read_text(encoding="utf-8") for p in SKILL.rglob("*.md")) + if re.search(r"\.Response\s*=", skill_text): + fail("docs use non-existent IResponseFeature.Response setter; use SetResponse(...)") + + for rel_path in REPO_PATHS: + if rel_path not in skill_text and not re.search(re.escape(Path(rel_path).name), skill_text): + print(f"WARN: repo path not mentioned explicitly: {rel_path}") + + print("OK: MinimalLambda skill references validated") + + +if __name__ == "__main__": + main() From 8b646141bd8211a3c0d93c3136861034933abac8 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Fri, 8 May 2026 18:40:37 -0400 Subject: [PATCH 09/13] fix: make MinimalLambda skill portable --- skills/minimal-lambda/SKILL.md | 19 +++-------- .../minimal-lambda/references/core-hosting.md | 27 ++------------- skills/minimal-lambda/references/envelopes.md | 23 ++----------- .../references/opentelemetry.md | 8 ++--- .../references/patterns/envelope-patterns.md | 2 +- skills/minimal-lambda/references/testing.md | 6 ++-- .../scripts/validate_references.py | 33 ++++--------------- 7 files changed, 23 insertions(+), 95 deletions(-) diff --git a/skills/minimal-lambda/SKILL.md b/skills/minimal-lambda/SKILL.md index f5b9d67a..2fad4578 100644 --- a/skills/minimal-lambda/SKILL.md +++ b/skills/minimal-lambda/SKILL.md @@ -20,8 +20,9 @@ Use this skill to give agents enough MinimalLambda project context without loadi - tracing/metrics/shutdown flush → read `references/opentelemetry.md` - compile/runtime/test failure → read `references/troubleshooting.md` - repo contribution/source generator/AOT work → read `references/repo-workflow.md` -2. Validate against local docs/code when in MinimalLambda repo. Prefer docs first, then source/tests for exact API. -3. Keep Lambda-first constraints in mind: source generation, AOT friendliness, scoped per-invocation services, one handler per runtime execution. +2. Use bundled references as the primary source. They are included so the skill works in client projects and global installs without assuming the MinimalLambda repository is present. +3. Only inspect local MinimalLambda source paths after confirming the current workspace is this repository; for repo contributions, read `references/repo-workflow.md` first. +4. Keep Lambda-first constraints in mind: source generation, AOT friendliness, scoped per-invocation services, one handler per runtime execution. ## Fast mental model @@ -49,19 +50,9 @@ Core pieces: - `MinimalLambda.Testing` runs real pipeline in memory for client project tests. - Envelope packages provide trigger-specific typed event/body access; use matching package rather than hand-parsing AWS records. -## Source-of-truth files +## Portability rule -When details matter, inspect these: - -- docs index: `docs/` -- package READMEs: `src/*/README.md`, `src/Envelopes/*/README.md` -- core runtime: `src/MinimalLambda/` -- abstractions: `src/MinimalLambda.Abstractions/` -- source generator: `src/MinimalLambda.SourceGenerators/` -- tests: `tests/` -- examples: `examples/` - -Use `rg` for exact APIs before changing code. Existing docs can lag implementation; code/tests win. +Assume this skill may run in a client project, not the MinimalLambda repository. Do not try to read MinimalLambda repo-local source, docs, test, or example paths unless the task is explicitly about changing MinimalLambda itself or the workspace clearly contains this repository. For client-project work, answer from bundled references and the user's project files. ## Common advice patterns diff --git a/skills/minimal-lambda/references/core-hosting.md b/skills/minimal-lambda/references/core-hosting.md index 0fda0276..c03a14b6 100644 --- a/skills/minimal-lambda/references/core-hosting.md +++ b/skills/minimal-lambda/references/core-hosting.md @@ -2,30 +2,9 @@ Read when task touches `LambdaApplication`, `MapHandler`, `[FromEvent]`, DI, lifecycle hooks, middleware, features, configuration, or source-generated handler behavior. -## Docs to consult - -- `README.md` -- `docs/getting-started/core-concepts.md` -- `docs/guides/handler-registration.md` -- `docs/guides/dependency-injection.md` -- `docs/guides/middleware.md` -- `docs/guides/lifecycle-management.md` -- `docs/guides/configuration.md` -- `src/MinimalLambda/README.md` -- `src/MinimalLambda.Abstractions/README.md` - -## Code to validate - -- `src/MinimalLambda/Builder/LambdaApplication.cs` -- `src/MinimalLambda/Builder/Extensions/BuilderLambdaApplicationExtensions.cs` -- `src/MinimalLambda/Builder/InterceptionTargets/MapHandlerLambdaApplicationExtensions.cs` -- `src/MinimalLambda/Builder/InterceptionTargets/UseMiddlewareLambdaApplicationExtensions.cs` -- `src/MinimalLambda/Builder/InterceptionTargets/OnInitLambdaApplicationExtensions.cs` -- `src/MinimalLambda/Builder/InterceptionTargets/OnShutdownLambdaApplicationExtensions.cs` -- `src/MinimalLambda/Core/Context/LambdaInvocationContext.cs` -- `src/MinimalLambda/Core/Features/*` -- `src/MinimalLambda/Core/Features/FeatureLambdaInvocationContextExtensions.cs` -- `src/MinimalLambda.SourceGenerators/*` +## Portability note + +This reference is self-contained for client-project use. Do not assume the MinimalLambda source tree exists in the current workspace. If the task is a repo contribution, switch to `repo-workflow.md` for local source landmarks. ## Builder shape diff --git a/skills/minimal-lambda/references/envelopes.md b/skills/minimal-lambda/references/envelopes.md index 7e1b90e2..69e7d6b3 100644 --- a/skills/minimal-lambda/references/envelopes.md +++ b/skills/minimal-lambda/references/envelopes.md @@ -2,26 +2,9 @@ Read when task touches SQS, SNS, API Gateway, Kinesis, Kinesis Firehose, Kafka/MSK, CloudWatch Logs, ALB, event bodies, typed payloads, or AWS trigger-specific request/response types. -## Docs to consult - -- `docs/features/envelopes.md` -- `src/Envelopes/README.md` -- `src/Envelopes/MinimalLambda.Envelopes/README.md` -- trigger package README: - - `src/Envelopes/MinimalLambda.Envelopes.Sqs/README.md` - - `src/Envelopes/MinimalLambda.Envelopes.Sns/README.md` - - `src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md` - - `src/Envelopes/MinimalLambda.Envelopes.Kinesis/README.md` - - `src/Envelopes/MinimalLambda.Envelopes.KinesisFirehose/README.md` - - `src/Envelopes/MinimalLambda.Envelopes.Kafka/README.md` - - `src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs/README.md` - - `src/Envelopes/MinimalLambda.Envelopes.Alb/README.md` - -## Code to validate - -- `src/Envelopes/MinimalLambda.Envelopes*/**/*.cs` -- `tests/MinimalLambda.Envelopes.UnitTests/` -- `examples/MinimalLambda.Example.Events/` +## Portability note + +This reference is self-contained for client-project use. Do not assume envelope package source or tests exist in the current workspace. If the task is a repo contribution, switch to `repo-workflow.md` before inspecting local source. ## Mental model diff --git a/skills/minimal-lambda/references/opentelemetry.md b/skills/minimal-lambda/references/opentelemetry.md index 89fa5004..77947142 100644 --- a/skills/minimal-lambda/references/opentelemetry.md +++ b/skills/minimal-lambda/references/opentelemetry.md @@ -2,13 +2,9 @@ Read when task asks for tracing, metrics, X-Ray/OTLP, `UseOpenTelemetryTracing`, AWS Lambda instrumentation, or telemetry flush on shutdown. -## Docs to consult +## Portability note -- `docs/features/open_telemetry.md` -- `src/MinimalLambda.OpenTelemetry/README.md` -- `examples/MinimalLambda.Example.OpenTelemetry/README.md` -- `src/MinimalLambda.OpenTelemetry/` -- `tests/MinimalLambda.OpenTelemetry.UnitTests/` +This reference is self-contained for client-project use. Do not assume MinimalLambda OpenTelemetry source, examples, or tests exist in the current workspace. If the task is a repo contribution, switch to `repo-workflow.md` before inspecting local source. ## Packages diff --git a/skills/minimal-lambda/references/patterns/envelope-patterns.md b/skills/minimal-lambda/references/patterns/envelope-patterns.md index 2e0e7b2c..c17a66bb 100644 --- a/skills/minimal-lambda/references/patterns/envelope-patterns.md +++ b/skills/minimal-lambda/references/patterns/envelope-patterns.md @@ -91,7 +91,7 @@ lambda.MapHandler(([FromEvent] KinesisFirehoseEventEnvelope envelop }); ``` -Firehose response contracts are easy to get wrong; validate against package tests/source. +Firehose response contracts are easy to get wrong; use the current package documentation or repo workflow before changing framework code. ## AOT envelope pattern diff --git a/skills/minimal-lambda/references/testing.md b/skills/minimal-lambda/references/testing.md index aa4cee29..addcc5a5 100644 --- a/skills/minimal-lambda/references/testing.md +++ b/skills/minimal-lambda/references/testing.md @@ -2,11 +2,9 @@ Read when task asks for integration tests, in-memory Lambda execution, test fixtures, host overrides, lifecycle tests, or client project test setup. -## Docs to consult +## Portability note -- `docs/guides/testing.md` -- `src/MinimalLambda.Testing/README.md` -- `tests/MinimalLambda.Testing.UnitTests/` +This reference is self-contained for client-project use. Do not assume MinimalLambda test-source paths exist in the current workspace. If the task is a repo contribution, switch to `repo-workflow.md` before inspecting local tests. ## Core idea diff --git a/skills/minimal-lambda/scripts/validate_references.py b/skills/minimal-lambda/scripts/validate_references.py index 108395bf..350d51f0 100755 --- a/skills/minimal-lambda/scripts/validate_references.py +++ b/skills/minimal-lambda/scripts/validate_references.py @@ -32,25 +32,6 @@ SKILL / "evals" / "evals.json", ] -REPO_PATHS = [ - "README.md", - "docs/getting-started/core-concepts.md", - "docs/guides/handler-registration.md", - "docs/guides/dependency-injection.md", - "docs/guides/middleware.md", - "docs/guides/lifecycle-management.md", - "docs/guides/configuration.md", - "docs/guides/testing.md", - "docs/features/envelopes.md", - "docs/features/open_telemetry.md", - "src/MinimalLambda/Builder/LambdaApplication.cs", - "src/MinimalLambda/Builder/Extensions/BuilderLambdaApplicationExtensions.cs", - "src/MinimalLambda/Builder/InterceptionTargets/MapHandlerLambdaApplicationExtensions.cs", - "src/MinimalLambda/Core/Features/FeatureLambdaInvocationContextExtensions.cs", - "src/MinimalLambda.Testing/README.md", - "src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md", -] - SYMBOL_CHECKS = { "LambdaApplication": "src/MinimalLambda/Builder/LambdaApplication.cs", "MapHandler": "src/MinimalLambda/Builder/InterceptionTargets/MapHandlerLambdaApplicationExtensions.cs", @@ -74,10 +55,6 @@ def main() -> None: if missing: fail("missing skill files:\n" + "\n".join(str(p) for p in missing)) - missing_repo = [ROOT / p for p in REPO_PATHS if not (ROOT / p).exists()] - if missing_repo: - fail("missing repo source/doc paths:\n" + "\n".join(str(p) for p in missing_repo)) - for symbol, rel_path in SYMBOL_CHECKS.items(): path = ROOT / rel_path if not path.exists(): @@ -89,9 +66,13 @@ def main() -> None: if re.search(r"\.Response\s*=", skill_text): fail("docs use non-existent IResponseFeature.Response setter; use SetResponse(...)") - for rel_path in REPO_PATHS: - if rel_path not in skill_text and not re.search(re.escape(Path(rel_path).name), skill_text): - print(f"WARN: repo path not mentioned explicitly: {rel_path}") + client_reference_text = "\n".join( + p.read_text(encoding="utf-8") + for p in SKILL.rglob("*.md") + if p.name != "repo-workflow.md" + ) + if re.search(r"`(?:src|docs|tests|examples)/", client_reference_text): + fail("client-facing skill references should not point at MinimalLambda repo-local paths") print("OK: MinimalLambda skill references validated") From 823c9b7393aa7d0f01624e8269a9f908fd6b7435 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 9 May 2026 08:34:19 -0400 Subject: [PATCH 10/13] chore: remove skill validation workspace --- .../iteration-1/validation-report.md | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 skills/minimal-lambda-workspace/iteration-1/validation-report.md diff --git a/skills/minimal-lambda-workspace/iteration-1/validation-report.md b/skills/minimal-lambda-workspace/iteration-1/validation-report.md deleted file mode 100644 index d7f45f87..00000000 --- a/skills/minimal-lambda-workspace/iteration-1/validation-report.md +++ /dev/null @@ -1,53 +0,0 @@ -# MinimalLambda skill validation report — iteration 1 - -## Scope - -Reviewed `skills/minimal-lambda` as dissemination skill for client-project agents, not repo-local `.agents` skill. - -## Validation method - -- Checked skill structure for progressive disclosure. -- Cross-checked referenced docs/source files in repo. -- Verified key API symbols exist in source: - - `LambdaApplication` - - `MapHandler` - - `FromEventAttribute` - - `AddLambdaSerializerWithContext` - - `ConfigureEnvelopeOptions` - - `LambdaApplicationFactory` - - common envelope types -- Added prompt eval set in `skills/minimal-lambda/evals/evals.json` for future skill-creator runs. -- Ran deterministic validation script: - -```bash -python3 skills/minimal-lambda/scripts/validate_references.py -``` - -Result: - -```text -OK: MinimalLambda skill references validated -``` - -## Content improvements made - -Added best-practice/project-use files: - -- `references/best-practices.md` — architecture, DI lifetimes, middleware/lifecycle/testing/AOT checklists. -- `references/client-project-setup.md` — package setup, `Program.cs` template, config, top-level Program testing note, AOT context. -- `references/troubleshooting.md` — generator/runtime/serialization/testing failure playbook. -- `references/patterns/handler-patterns.md` — thin handlers, no-event handlers, keyed services, direct unit tests. -- `references/patterns/middleware-patterns.md` — inline/class middleware, features, short-circuit cache, error boundary. -- `references/patterns/envelope-patterns.md` — exact envelope type table and trigger examples. -- `references/patterns/testing-patterns.md` — `LambdaApplicationFactory`, overrides, invocation APIs, fixture guidance. -- `references/patterns/aot-and-envelopes.md` — serializer context and envelope options patterns. - -Updated: - -- `SKILL.md` task routing now points to new best-practice/pattern/troubleshooting files. -- `references/envelopes.md` now includes exact common envelope type names and AOT serializer guidance. -- `references/core-hosting.md` now points at exact feature helper source file. - -## Gaps / future eval - -Full with-skill vs baseline evals were not run because this harness exposes no subagent task tool. Evals are ready in `evals/evals.json` for a future skill-creator runner. Deterministic reference validation passed. From 0e00b02b7ea23ca38b86b3541de6b609722d93b0 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 9 May 2026 08:59:06 -0400 Subject: [PATCH 11/13] chore(git-workflow): remove outdated workflow documentation - Deleted outdated workflow files for branch, commit, and PR processes. - Removed shared references, templates, and examples tied to legacy workflows. - Cleared obsolete `SKILL.md` content under `.agents/skills/git-workflow`. - Retired unused Git workflow examples and guides to align with current tooling. --- .agents/skills/git-workflow/shared/conventional-types.md | 2 +- .agents/skills/git-workflow/shared/scope-detection.md | 2 +- skills-lock.json | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.agents/skills/git-workflow/shared/conventional-types.md b/.agents/skills/git-workflow/shared/conventional-types.md index 76a0bd25..e1d40033 100644 --- a/.agents/skills/git-workflow/shared/conventional-types.md +++ b/.agents/skills/git-workflow/shared/conventional-types.md @@ -3,7 +3,7 @@ Valid `` values: | Type | Description | SemVer impact | -|------------|-------------------------------------------------------------------|---------------| +| ---------- | ----------------------------------------------------------------- | ------------- | | `feat` | Introduces a new feature | MINOR | | `fix` | Patches a bug | PATCH | | `build` | Changes to the build system or external dependencies | — | diff --git a/.agents/skills/git-workflow/shared/scope-detection.md b/.agents/skills/git-workflow/shared/scope-detection.md index 6bb4c29c..95df6d0a 100644 --- a/.agents/skills/git-workflow/shared/scope-detection.md +++ b/.agents/skills/git-workflow/shared/scope-detection.md @@ -3,7 +3,7 @@ Infer scope from the folder containing the majority of the changes. | Folder | Scope | -|-------------------------------|---------------------| +| ----------------------------- | ------------------- | | `.github/workflows` | `github` | | `src/Core` | `core` | | `src/Abstractions` | `abstractions` | diff --git a/skills-lock.json b/skills-lock.json index f2894084..f76e4538 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -4,11 +4,13 @@ "git-workflow": { "source": "LayeredCraft/skills", "sourceType": "github", + "skillPath": "skills/git-workflow/SKILL.md", "computedHash": "fbd78cda46da7d8753beca0d68ce6532589daa4da0a457aa709fe1e11da468fe" }, "zensical-site": { "source": "LayeredCraft/skills", "sourceType": "github", + "skillPath": "skills/zensical-site/SKILL.md", "computedHash": "f66b47704234c8242d45a52e3d92d5307f49254e189819165ee37845da64e9c8" } } From 378372562b74706e7464a47c2885f313dea10a60 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 9 May 2026 09:04:53 -0400 Subject: [PATCH 12/13] docs: fix docs formatting and local serve command --- docs/index.md | 7 +++---- tasks/LocalDevTasks.yml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/index.md b/docs/index.md index 43535121..cbb95250 100644 --- a/docs/index.md +++ b/docs/index.md @@ -180,6 +180,9 @@ await lambda.RunAsync(); !!! tip "Next Steps" + Ready to dive deeper? Check out the [Getting Started Guide](getting-started/index.md) for a complete + tutorial, or explore the [Examples](examples/index.md) to see real-world applications. + ## AI Agent Skill Use the bundled `minimal-lambda` agent skill to give coding agents focused MinimalLambda guidance @@ -197,10 +200,6 @@ For a global Claude Code install: npx skills add j-d-ha/minimal-lambda --skill minimal-lambda --global --agent claude-code ``` ---- - Ready to dive deeper? Check out the [Getting Started Guide](getting-started/index.md) for a complete - tutorial, or explore the [Examples](examples/index.md) to see real-world applications. - ______________________________________________________________________ ## Packages diff --git a/tasks/LocalDevTasks.yml b/tasks/LocalDevTasks.yml index 50d60db9..fdda65ea 100644 --- a/tasks/LocalDevTasks.yml +++ b/tasks/LocalDevTasks.yml @@ -59,11 +59,11 @@ tasks: - dotnet lambda-test-tool start --lambda-emulator-port 5050 --config-storage-path ./lambda_test_tool docs:serve: - desc: Serve docs locally with Zensical + desc: Serve docs locally with Zensical and open browser silent: true cmds: - echo "📖 Serving Docs" - - uv run zensical serve -f mkdocs.yml + - uv run zensical serve -f mkdocs.yml --open docs:build: desc: Build docs site with Zensical From bfc365aec1307869bb48ca1e2b5ad165e72d13df Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sat, 9 May 2026 09:37:50 -0400 Subject: [PATCH 13/13] docs(minimal-lambda): add lifecycle hook patterns and refine handler/middleware guidance - Introduced `lifecycle-hook-patterns.md` covering `OnInit` and `OnShutdown` best practices. - Linked lifecycle hook references in `SKILL.md` and clarified usage guidelines. - Enhanced `handler-patterns.md` with inline arrow function examples for MinimalLambda adapters. - Updated `middleware-patterns.md` to encourage inline glue logic but explain when to extract classes. - Added small refinements to envelope, hosting, and testing references to ensure alignment. - Improved guidance on keeping Lambda context objects at the edge in handler/middleware/hooks. --- skills/minimal-lambda/SKILL.md | 17 +++- skills/minimal-lambda/evals/evals.json | 25 +++-- .../references/best-practices.md | 64 +++++++----- .../references/client-project-setup.md | 18 ++-- .../minimal-lambda/references/core-hosting.md | 40 +++++--- skills/minimal-lambda/references/envelopes.md | 15 ++- .../references/opentelemetry.md | 4 +- .../references/patterns/envelope-patterns.md | 4 +- .../references/patterns/handler-patterns.md | 98 ++++++++++++------- .../patterns/lifecycle-hook-patterns.md | 95 ++++++++++++++++++ .../patterns/middleware-patterns.md | 18 +++- .../references/repo-workflow.md | 5 +- .../references/troubleshooting.md | 2 +- 13 files changed, 305 insertions(+), 100 deletions(-) create mode 100644 skills/minimal-lambda/references/patterns/lifecycle-hook-patterns.md diff --git a/skills/minimal-lambda/SKILL.md b/skills/minimal-lambda/SKILL.md index 2fad4578..c70361f0 100644 --- a/skills/minimal-lambda/SKILL.md +++ b/skills/minimal-lambda/SKILL.md @@ -14,6 +14,8 @@ Use this skill to give agents enough MinimalLambda project context without loadi - app setup/handler/DI/lifecycle → read `references/core-hosting.md` and `references/best-practices.md` - handler shape/unit-testable handlers → read `references/patterns/handler-patterns.md` - middleware/features/context → read `references/core-hosting.md` and `references/patterns/middleware-patterns.md` + - lifecycle hooks (`OnInit`/`OnShutdown`) → read `references/core-hosting.md` and + `references/patterns/lifecycle-hook-patterns.md` - SQS/SNS/API Gateway/Kinesis/Firehose/Kafka/CloudWatch/ALB envelopes → read `references/envelopes.md` and `references/patterns/envelope-patterns.md` - Native AOT/trimming/serializer context → read `references/patterns/aot-and-envelopes.md` - integration tests/client project tests → read `references/testing.md` and `references/patterns/testing-patterns.md` @@ -58,13 +60,24 @@ Assume this skill may run in a client project, not the MinimalLambda repository. Read `references/best-practices.md` before giving architectural advice. -- Prefer thin handlers delegating to injected services. +- Prefer inline `MapHandler` arrow functions, inline middleware, and inline lifecycle hooks in + `Program.cs` when they are Lambda adapter/glue code. +- Keep complex business logic out of handlers, middleware, and hooks; put it in injected services or + small domain helpers. +- Treat `ILambdaInvocationContext`, raw AWS `ILambdaContext`, features, lifecycle context, and other + Lambda context objects as edge concerns. Almost never pass them into services; extract needed + primitive/domain values at the edge. Passing Lambda context into services is usually a + layer-boundary smell. +- Allow simple inline logic in `Program.cs` when logic is tiny and Lambda remains easy to read. +- Extract middleware classes only when middleware is complex, reusable, stateful, or worth testing + separately. - Prefer `CancellationToken` in async handlers and downstream calls. - Prefer scoped services for per-invocation state; singleton for reusable clients/caches. - Avoid storing scoped services in singletons. - Prefer typed records/responses/envelopes over anonymous response contracts. - Keep AOT/trimming safe: avoid reflection-heavy dynamic paths unless guarded and tested. -- Use method-group handlers or static handler methods when unit-testing handler logic directly. +- For direct unit tests, test services/helpers; only extract a named handler when handler adapter + logic itself needs focused tests. - For end-to-end behavior, use `LambdaApplicationFactory`. ## Validation checklist diff --git a/skills/minimal-lambda/evals/evals.json b/skills/minimal-lambda/evals/evals.json index 785ce14e..8fd02eed 100644 --- a/skills/minimal-lambda/evals/evals.json +++ b/skills/minimal-lambda/evals/evals.json @@ -3,11 +3,13 @@ "evals": [ { "id": 1, - "prompt": "I have a .NET Lambda using MinimalLambda. Create a clean Program.cs for an order processor with DI, a static method-group handler, cancellation token propagation, and a unit-testable service boundary. Also mention what package/usings I need.", - "expected_output": "Uses LambdaApplication.CreateBuilder, registers services before Build, maps exactly one method-group handler with one [FromEvent] OrderRequest parameter, injects IOrderService and CancellationToken, calls RunAsync, and explains MinimalLambda package/usings.", + "prompt": "I have a .NET Lambda using MinimalLambda. Create a clean Program.cs for an order processor with DI, an inline MapHandler arrow function, cancellation token propagation, and a service boundary for non-Lambda business logic. Also mention what package/usings I need.", + "expected_output": "Uses LambdaApplication.CreateBuilder, registers services before Build, maps exactly one inline handler with one [FromEvent] OrderRequest parameter, injects IOrderService and CancellationToken, delegates business logic to the service, calls RunAsync, and explains MinimalLambda package/usings.", "files": [], "assertions": [ - { "text": "Output uses method-group handler pattern rather than inline business logic." }, + { + "text": "Output uses inline MapHandler arrow function as the Lambda adapter and delegates non-trivial business logic to a service." + }, { "text": "Handler signature has exactly one [FromEvent] payload parameter and propagates CancellationToken." }, @@ -31,8 +33,8 @@ }, { "id": 3, - "prompt": "My MinimalLambda middleware isn't seeing the order request and sometimes scoped data leaks between invocations. Review likely causes and show a better middleware pattern.", - "expected_output": "Explains middleware order before MapHandler, uses ILambdaInvocationContext feature helpers like TryGetEvent/GetResponse, distinguishes Items vs Properties, warns against singleton/scoped leaks, and shows thin middleware pattern.", + "prompt": "My MinimalLambda middleware isn't seeing the order request and sometimes scoped data leaks between invocations. Review likely causes and show a better inline middleware pattern, noting when to extract a class or delegate to a service.", + "expected_output": "Explains middleware order before MapHandler, uses ILambdaInvocationContext feature helpers like TryGetEvent/GetResponse, distinguishes Items vs Properties, warns against singleton/scoped leaks, shows thin inline middleware, says to extract class middleware or services only when logic is complex/reusable/stateful/test-worthy, and keeps Lambda context objects out of services.", "files": [], "assertions": [ { "text": "Output says middleware should be registered before MapHandler." }, @@ -40,7 +42,18 @@ { "text": "Output distinguishes per-invocation Items from cross-invocation Properties/singletons." }, - { "text": "Output warns against capturing scoped services/state in singletons." } + { + "text": "Output warns against capturing scoped services/state in singletons." + }, + { + "text": "Output prefers inline middleware for simple Lambda glue and extracts class middleware/services only for complex, reusable, stateful, or separately tested logic." + }, + { + "text": "Output says inline middleware does not support direct service injection and uses context.ServiceProvider, class middleware, or factory middleware for dependencies." + }, + { + "text": "Output does not pass ILambdaInvocationContext, ILambdaContext, or feature collections into application services; it extracts needed domain values at the edge." + } ] }, { diff --git a/skills/minimal-lambda/references/best-practices.md b/skills/minimal-lambda/references/best-practices.md index 482e7e14..5b194769 100644 --- a/skills/minimal-lambda/references/best-practices.md +++ b/skills/minimal-lambda/references/best-practices.md @@ -4,13 +4,15 @@ Read when designing client-project code, reviewing MinimalLambda usage, or decid ## Default architecture -Use three layers: +Use `Program.cs` as Lambda edge: -1. `Program.cs` wires host, configuration, services, middleware, lifecycle hooks, and one handler. -2. Handler method adapts Lambda event to application service call. -3. Application services contain business logic and can be unit-tested without Lambda host. +1. `Program.cs` wires host, configuration, services, inline middleware, lifecycle hooks, and one + inline `MapHandler` arrow function. +2. Handler/middleware/hooks adapt Lambda concerns: bind payload, access invocation context, set + scopes/features/responses, call application service/helper, return Lambda response. +3. Application services contain complex business logic and can be unit-tested without Lambda host. -Good shape: +Good shape for real business logic: ```csharp var builder = LambdaApplication.CreateBuilder(); @@ -19,21 +21,14 @@ builder.Services.AddScoped(); await using var lambda = builder.Build(); -lambda.MapHandler(OrderHandlers.HandleAsync); +lambda.MapHandler(([FromEvent] OrderRequest request, IOrderService orders, CancellationToken ct) => + orders.ProcessAsync(request, ct)); await lambda.RunAsync(); - -internal static class OrderHandlers -{ - public static Task HandleAsync( - [FromEvent] OrderRequest request, - IOrderService orders, - CancellationToken ct) => - orders.ProcessAsync(request, ct); -} ``` -Why: source generator sees stable handler signature, handler stays testable, Lambda-specific code stays at edge. +Why: handler remains visibly Lambda-shaped and local to startup, while non-Lambda decisions live +behind services/helpers. ## Handler best practices @@ -41,17 +36,26 @@ Prefer: - one clear `[FromEvent]` payload parameter when event exists - explicit typed request/response records -- method-group handlers for direct unit tests +- inline `MapHandler` arrow functions for Lambda adapter code - `CancellationToken` in async handlers - injected services instead of resolving from `IServiceProvider` +- extracting needed values from `ILambdaInvocationContext`/`ILambdaContext` at the edge before + calling services - throwing meaningful exceptions for unrecoverable invalid state Avoid: +- complex business rules, orchestration, validation workflows, or persistence logic inside the + handler +- extracting named handler classes just to hold a one-line adapter - anonymous response contracts in public APIs - multiple runtime `MapHandler` calls - manually parsing event JSON when envelope package exists - reflection-heavy dispatch/routing inside one Lambda unless absolutely needed +- passing `ILambdaInvocationContext`, raw AWS `ILambdaContext`, lifecycle context, feature + collections, or Lambda context wrappers into application services +- injecting Lambda context into services; this should be almost never and treated as a + layer-boundary smell unless explicitly isolated and justified - storing `ILambdaInvocationContext` or scoped services beyond invocation ## DI lifetime choices @@ -66,7 +70,13 @@ Never capture scoped services in singleton state. Lambda warm reuse makes leaks ## Middleware best practices -Use middleware for cross-cutting invocation concerns: +Use middleware for cross-cutting invocation concerns. Prefer inline middleware in `Program.cs` when +it is app-local Lambda glue. Inline `UseMiddleware(async (context, next) => ...)` receives only +`ILambdaInvocationContext` and `next`; it does not support handler-style direct parameter injection. +Resolve simple dependencies from `context.ServiceProvider`, use `UseMiddleware()` class +middleware for constructor DI, or use `UseMiddleware()` when construction must be +custom/deferred per invocation. Services called from middleware should receive normal domain values, +not Lambda context objects: - logging scopes/correlation - auth/authz @@ -83,7 +93,8 @@ Ordering: 4. idempotency/caching 5. handler -Keep inline middleware thin. Extract reusable or stateful logic to `ILambdaMiddleware` classes. +Keep inline middleware thin. If logic becomes complex, reusable, stateful, or needs direct unit +tests, extract an `ILambdaMiddleware` class or delegate business work to an injected service. ## Lifecycle best practices @@ -99,7 +110,10 @@ Use `OnShutdown` for bounded cleanup: - drain buffers - release external leases -Keep both cancellation-aware. `OnInit` failures should be intentional because failed init prevents serving invocations. +Keep both cancellation-aware. Inline Lambda-specific or tiny hook logic in `Program.cs`; delegate +complex warmup/flush/validation to DI services. Hook delegate overloads support direct DI +parameters, but pass services only the data they need, not lifecycle/context objects. `OnInit` +failures should be intentional because failed init prevents serving invocations. ## Event source decision guide @@ -110,7 +124,8 @@ Keep both cancellation-aware. `OnInit` failures should be intentional because fa ## Testing strategy -- Unit-test services and static handler methods directly. +- Unit-test services/helpers directly; avoid unit-testing generated binding through handler + extraction. - Use `MinimalLambda.Testing` for pipeline behavior: source-generated binding, middleware, DI scopes, lifecycle, envelopes, serialization, error payloads. - Share `LambdaApplicationFactory` only when singleton/lifecycle sharing is acceptable. @@ -119,7 +134,7 @@ Keep both cancellation-aware. `OnInit` failures should be intentional because fa Prefer: - source-generated JSON contexts -- static handler methods +- inline, analyzable handler delegates - explicit contracts - package APIs built for source generation @@ -134,6 +149,11 @@ Avoid: - [ ] One runtime handler mapping. - [ ] Payload parameter has exactly one `[FromEvent]` or no payload at all. - [ ] Middleware registered before handler mapping. +- [ ] Handler, middleware, and hooks contain Lambda adapter/glue work only, unless logic is tiny + enough to stay readable inline. +- [ ] Complex business logic lives in injected services/helpers, not handler/middleware/hook bodies. +- [ ] Lambda context objects stay at the edge; services receive domain values/options/cancellation + tokens instead. - [ ] Async work accepts and propagates cancellation token. - [ ] DI lifetimes match Lambda warm-container reuse. - [ ] Envelope package matches AWS trigger. diff --git a/skills/minimal-lambda/references/client-project-setup.md b/skills/minimal-lambda/references/client-project-setup.md index 567da0d0..182100b8 100644 --- a/skills/minimal-lambda/references/client-project-setup.md +++ b/skills/minimal-lambda/references/client-project-setup.md @@ -34,7 +34,9 @@ Keep `MinimalLambda.Testing` version aligned with `MinimalLambda`. ## Project file basics -Use C# 11+ because interceptors/source generation paths require modern language support. Repo uses newer versions; client project can use current SDK/LangVersion. +Use a current .NET SDK and modern C# language version. Interceptors/source-generation paths require +compiler support; prefer `LangVersion` `latest`/`preview` when package docs or build diagnostics +require it. Repo uses newer versions; client project can use current SDK/LangVersion. ```xml @@ -60,22 +62,14 @@ builder.Services.AddScoped(); await using var lambda = builder.Build(); -lambda.MapHandler(OrderHandlers.HandleAsync); +lambda.MapHandler(([FromEvent] OrderRequest request, IOrderService orders, CancellationToken cancellationToken) => + orders.ProcessAsync(request, cancellationToken)); await lambda.RunAsync(); public sealed record OrderRequest(string OrderId); public sealed record OrderResponse(string OrderId, bool Accepted); -internal static class OrderHandlers -{ - public static Task HandleAsync( - [FromEvent] OrderRequest request, - IOrderService orders, - CancellationToken cancellationToken) => - orders.ProcessAsync(request, cancellationToken); -} - internal interface IOrderService { Task ProcessAsync(OrderRequest request, CancellationToken cancellationToken); @@ -150,6 +144,6 @@ For envelope payloads, also configure envelope options. See `patterns/aot-and-en 2. Add `MinimalLambda.Builder` using for builder + `[FromEvent]`. 3. Add service registrations before `Build()`. 4. Add middleware before `MapHandler`. -5. Add exactly one handler mapping path. +5. Ensure exactly one `MapHandler` path executes at runtime. 6. Add serializer context for AOT or explicit serialization requirements. 7. Add integration test using `MinimalLambda.Testing` when pipeline behavior matters. diff --git a/skills/minimal-lambda/references/core-hosting.md b/skills/minimal-lambda/references/core-hosting.md index c03a14b6..840620ae 100644 --- a/skills/minimal-lambda/references/core-hosting.md +++ b/skills/minimal-lambda/references/core-hosting.md @@ -29,7 +29,7 @@ await lambda.RunAsync(); `CreateBuilder()` wires standard .NET configuration/logging/DI defaults unless `LambdaApplicationOptions.DisableDefaults = true`. -Configuration provider order from docs: +Configuration provider order from docs; later providers override earlier values: 1. `AWS_` env vars 2. `DOTNET_` env vars @@ -52,19 +52,12 @@ Framework options bind from `LambdaHost` section, not old `AwsLambdaHost`. Good handler style: ```csharp -lambda.MapHandler(MyHandlers.HandleAsync); - -internal static class MyHandlers -{ - public static Task HandleAsync( - [FromEvent] OrderRequest request, - IOrderService service, - CancellationToken ct) => - service.ProcessAsync(request, ct); -} +lambda.MapHandler(([FromEvent] OrderRequest request, IOrderService service, CancellationToken ct) => + service.ProcessAsync(request, ct)); ``` -Method groups keep handler logic unit-testable. +Keep handler inline in `Program.cs` as Lambda adapter code. Put complex business logic in +services/helpers; tiny obvious logic may stay inline. ## DI and lifetimes @@ -85,6 +78,12 @@ Method groups keep handler logic unit-testable. - `Features` typed feature collection - also exposes AWS Lambda context members +Keep `ILambdaInvocationContext`, raw AWS `ILambdaContext`, lifecycle context, and feature +collections at the Lambda edge. Handlers/middleware/hooks may read them, then pass primitive/domain +values to services (for example `awsRequestId`, tenant id, headers, payload fields). Almost never +make application services depend on Lambda context types; that usually means Lambda boundary +concerns leaked into application layers. + Useful feature helpers from docs: ```csharp @@ -99,7 +98,13 @@ Use features in middleware to avoid coupling middleware directly to handlers. Register before `MapHandler`. Execution order follows registration order and unwinds in reverse. -Inline middleware: quick app-specific glue. +Inline middleware: quick app-specific glue. Inline middleware receives +`(ILambdaInvocationContext context, LambdaInvocationDelegate next)` only; it does not support direct +service injection like `MapHandler`/hooks. Use `context.ServiceProvider` for simple dependencies, +`UseMiddleware()` class middleware for constructor DI, or `UseMiddleware()` factory +middleware for custom/deferred per-invocation construction. Class middleware constructor parameters +can be resolved from DI and/or explicit `UseMiddleware(args)` values; use `[FromServices]` or +`[FromArguments]` when resolution must be forced. ```csharp lambda.UseMiddleware(async (context, next) => @@ -111,6 +116,9 @@ lambda.UseMiddleware(async (context, next) => }); ``` +Keep middleware inline when it is app-local Lambda glue. Use class middleware when it becomes +complex, reusable, stateful, or worth direct unit tests. + Class middleware: reusable/testable. ```csharp @@ -164,6 +172,12 @@ lambda.OnShutdown(async (ITelemetrySink sink, CancellationToken ct) => }); ``` +Keep hooks inline when they are Lambda lifecycle glue or tiny. Hook delegates support DI parameters, +so prefer `lambda.OnInit((ICache cache, CancellationToken ct) => ...)` over manual service +resolution. Delegate complex warmup, health validation, telemetry flushing, or external cleanup to +DI services. Do not pass lifecycle/context objects to services; pass cancellation tokens and +domain/config values. + ## Options Use `builder.Services.ConfigureLambdaHostOptions(options => { ... })`. diff --git a/skills/minimal-lambda/references/envelopes.md b/skills/minimal-lambda/references/envelopes.md index 69e7d6b3..b7e89a14 100644 --- a/skills/minimal-lambda/references/envelopes.md +++ b/skills/minimal-lambda/references/envelopes.md @@ -63,7 +63,9 @@ lambda.MapHandler(async ([FromEvent] SqsEnvelope envelope, IOrderS }); ``` -When exact type/member names matter, inspect target package README and source. Names vary by trigger. +When exact type/member names matter, inspect target package README/source if available. In client +projects/global installs where source is unavailable, rely on bundled references plus installed +package docs/IntelliSense. Names vary by trigger. ## AOT / serialization guidance @@ -79,7 +81,10 @@ See `patterns/aot-and-envelopes.md` for complete snippets. ## Agent workflow for envelope questions 1. Identify AWS event source. -2. Read matching envelope README. -3. Inspect source/tests for exact type/member names. -4. Propose minimal package references and handler signature. -5. Add/adjust tests using `MinimalLambda.Testing` or existing envelope unit test style. +2. Read matching envelope README/package docs when available. +3. Inspect source/tests for exact type/member names only when available; otherwise use package + docs/IntelliSense. +4. Check production batch semantics for queue/stream triggers, especially partial-batch failure + support. +5. Propose minimal package references and handler signature. +6. Add/adjust tests using `MinimalLambda.Testing` or existing envelope unit test style. diff --git a/skills/minimal-lambda/references/opentelemetry.md b/skills/minimal-lambda/references/opentelemetry.md index 77947142..6b02737e 100644 --- a/skills/minimal-lambda/references/opentelemetry.md +++ b/skills/minimal-lambda/references/opentelemetry.md @@ -78,4 +78,6 @@ await lambda.RunAsync(); 3. Configure `AddOpenTelemetry()` with tracing/metrics. 4. Add `UseOpenTelemetryTracing()` and shutdown flush. 5. Validate source names/meters match app instrumentation. -6. For tests, inspect OpenTelemetry unit tests for exact assertions/patterns. +6. For tests, inspect OpenTelemetry unit tests for exact assertions/patterns only when working in + this repo or when test sources are available; otherwise use bundled references and public package + docs. diff --git a/skills/minimal-lambda/references/patterns/envelope-patterns.md b/skills/minimal-lambda/references/patterns/envelope-patterns.md index c17a66bb..e1c86efc 100644 --- a/skills/minimal-lambda/references/patterns/envelope-patterns.md +++ b/skills/minimal-lambda/references/patterns/envelope-patterns.md @@ -17,7 +17,9 @@ Read when selecting or implementing strongly typed AWS trigger envelopes. | Kafka/MSK | `KafkaEnvelope` | usually no response | | CloudWatch Logs | `CloudWatchLogsEnvelope` or `CloudWatchLogsEnvelope` | usually no response | -Always inspect matching README/source for current property names and special cases. +Always inspect matching README/source for current property names and special cases when available. +In client projects/global installs without source, use bundled references plus package +docs/IntelliSense. ## API Gateway result pattern diff --git a/skills/minimal-lambda/references/patterns/handler-patterns.md b/skills/minimal-lambda/references/patterns/handler-patterns.md index 7122795b..33497e50 100644 --- a/skills/minimal-lambda/references/patterns/handler-patterns.md +++ b/skills/minimal-lambda/references/patterns/handler-patterns.md @@ -2,28 +2,65 @@ Read when implementing or reviewing handler shape. -## Thin method-group handler +## Default: inline Lambda adapter in `Program.cs` -Best default for client projects. +Prefer mapping an inline arrow function in `Program.cs` for normal MinimalLambda apps. ```csharp -lambda.MapHandler(OrderHandlers.HandleAsync); +lambda.MapHandler(([FromEvent] OrderRequest request, IOrderService orders, CancellationToken ct) => + orders.ProcessAsync(request, ct)); +``` -internal static class OrderHandlers -{ - public static Task HandleAsync( - [FromEvent] OrderRequest request, - IOrderService orders, - CancellationToken ct) => - orders.ProcessAsync(request, ct); -} +Handler job: Lambda edge only. + +- Bind payload with `[FromEvent]`. +- Accept Lambda/context parameters only when needed. +- Keep Lambda context objects at edge; pass services domain values, not `ILambdaInvocationContext`/ + `ILambdaContext`. +- Accept injected services explicitly. +- Pass `CancellationToken` through. +- Return response shape. + +Business job: service/helper. + +- validation workflows +- authorization/business policy decisions +- persistence +- external API orchestration +- transformations large enough to need names/tests + +Why: readers see whole Lambda entry point in one place, source generator gets analyzable signature, +business code remains independent of Lambda. + +## Complex business logic: delegate immediately + +```csharp +var builder = LambdaApplication.CreateBuilder(); + +builder.Services.AddScoped(); + +await using var lambda = builder.Build(); + +lambda.MapHandler(([FromEvent] OrderRequest request, IOrderService orders, CancellationToken ct) => + orders.ProcessAsync(request, ct)); + +await lambda.RunAsync(); ``` -Why: +Do not move complex logic into a named handler class just to hide it. Move it into app +services/domain helpers because that code is not Lambda-specific. + +## Tiny logic: keep it inline + +Small, obvious logic is fine directly in `Program.cs`. + +```csharp +lambda.MapHandler(([FromEvent] PingRequest request) => + new PingResponse(request.Message.Trim(), DateTimeOffset.UtcNow)); +``` -- source generator gets explicit signature -- handler can be unit-tested directly -- business logic stays in service +Keep inline when it is easier to read than a service, has no persistence/external orchestration, and +probably does not need isolated unit tests. ## No-event handler @@ -50,11 +87,16 @@ lambda.MapHandler(async ( CancellationToken ct) => { context.Items["OrderId"] = request.OrderId; - return await orders.ProcessAsync(request, context.AwsRequestId, ct); + context.Items["AwsRequestId"] = context.AwsRequestId; + return await orders.ProcessAsync(request, ct); }); ``` -Use context sparingly. Prefer services for business operations. +Use context sparingly. Prefer services for business operations. Do not pass +`ILambdaInvocationContext`, raw AWS `ILambdaContext`, features, or Lambda wrappers into +domain/application services. Extract needed values (`AwsRequestId`, deadline, tenant id, claims, +headers) in handler/middleware and pass those values instead. Only isolate a service behind Lambda +context when boundary cannot be expressed otherwise. ## Keyed service handler @@ -72,25 +114,13 @@ lambda.MapHandler(( Keep keys simple constants. -## Unit-testable handler method +## Testing guidance -```csharp -[Fact] -public async Task HandleAsync_ReturnsAcceptedOrder() -{ - var orders = Substitute.For(); - var request = new OrderRequest("order-123"); - var expected = new OrderResponse("order-123", Accepted: true); - - orders.ProcessAsync(request, Arg.Any()).Returns(expected); - - var actual = await OrderHandlers.HandleAsync(request, orders, TestContext.Current.CancellationToken); - - actual.Should().Be(expected); -} -``` +Unit-test services/helpers for business behavior. Use integration tests for source-generated +binding, DI, middleware, envelopes, and serialization. -Use integration tests for source-generated binding; direct unit tests for business behavior. +Extract a named static handler only when the adapter itself has enough Lambda-specific branching to +deserve direct unit tests. That should be uncommon. ## Anti-pattern: routing many event shapes in one handler diff --git a/skills/minimal-lambda/references/patterns/lifecycle-hook-patterns.md b/skills/minimal-lambda/references/patterns/lifecycle-hook-patterns.md new file mode 100644 index 00000000..37293a0b --- /dev/null +++ b/skills/minimal-lambda/references/patterns/lifecycle-hook-patterns.md @@ -0,0 +1,95 @@ +# Lifecycle hook patterns + +Read when adding or reviewing `OnInit` and `OnShutdown` hooks. + +## Default: inline lifecycle glue in `Program.cs` + +Use hooks for Lambda lifecycle concerns: + +- cold-start cache warmup +- required configuration/secrets validation +- expensive singleton preflight +- telemetry/buffer flush during shutdown +- bounded external cleanup + +Keep hooks inline when logic is small or Lambda-specific. + +```csharp +lambda.OnInit(async (ICacheWarmer warmer, CancellationToken ct) => +{ + await warmer.WarmAsync(ct); + return true; +}); + +lambda.OnShutdown(async (ITelemetrySink telemetry, CancellationToken ct) => +{ + await telemetry.FlushAsync(ct); +}); +``` + +Unlike inline middleware, lifecycle hook delegate overloads support direct DI parameters. Prefer DI +parameters over manual service resolution. + +## Delegate complex work to services + +Hooks should coordinate lifecycle; services should do real work. + +```csharp +lambda.OnInit(async (IStartupChecks checks, CancellationToken ct) => + await checks.ValidateAsync(ct)); +``` + +Good service inputs: + +- `CancellationToken` +- options/config values +- primitive/domain values +- typed abstractions like `ICacheWarmer`, `IStartupChecks`, `ITelemetrySink` + +Avoid service inputs: + +- `ILambdaLifecycleContext` +- `ILambdaInvocationContext` +- raw AWS `ILambdaContext` +- feature collections or Lambda wrappers + +Passing lifecycle/context objects into services is almost always a layer-boundary smell. Extract +values at the Lambda edge if a service needs them. + +## `OnInit` return values + +Return `bool` only when startup should be able to abort. + +```csharp +lambda.OnInit(async (IStartupChecks checks, CancellationToken ct) => +{ + var ok = await checks.ValidateAsync(ct); + return ok; +}); +``` + +- `true` continues startup. +- `false` aborts startup and prevents serving invocations. +- no return value implies success. + +Use `false` intentionally; failed init means Lambda should not process events. + +## Cancellation and time bounds + +Always accept/pass `CancellationToken` for async hook work. + +- `OnInit` token is bounded by `LambdaHostOptions.InitTimeout`. +- `OnShutdown` token is bounded by shutdown duration/buffer. +- Downstream services should honor cancellation promptly. + +## Shared state + +Lifecycle hooks run outside normal invocation flow. + +- `OnInit` runs once per execution environment. +- `OnShutdown` runs once during teardown. +- Each hook handler gets fresh scope. +- Warm containers reuse singleton state after init. + +Do not use lifecycle hooks for per-invocation state. Use scoped services or middleware for +invocation data. diff --git a/skills/minimal-lambda/references/patterns/middleware-patterns.md b/skills/minimal-lambda/references/patterns/middleware-patterns.md index f43b00e8..a3b1704b 100644 --- a/skills/minimal-lambda/references/patterns/middleware-patterns.md +++ b/skills/minimal-lambda/references/patterns/middleware-patterns.md @@ -2,6 +2,17 @@ Read when adding logging, metrics, validation, auth, idempotency, response mapping, or feature access. +Default: inline app-local middleware in `Program.cs`. Middleware should mostly be Lambda pipeline +glue: read context/features, set scopes/items/responses, call `next`, or delegate to services. +Inline middleware receives only `context` and `next`; it does not support direct service injection. +Resolve simple dependencies from `context.ServiceProvider`, use `UseMiddleware()` class +middleware when constructor DI is cleaner, or use `UseMiddleware()` when construction must +be custom/deferred per invocation. Class middleware constructor parameters can come from DI and/or +explicit `UseMiddleware(args)` values; use `[FromServices]` or `[FromArguments]` to remove +ambiguity. Keep Lambda context objects at this edge; services should receive domain values, options, +and `CancellationToken`, not `ILambdaInvocationContext`/`ILambdaContext`. Extract class middleware +when logic is complex, reusable, stateful, or needs direct unit tests. + ## Inline logging/correlation ```csharp @@ -71,7 +82,8 @@ internal sealed class TimingMiddleware(ILogger logger) : ILamb lambda.UseMiddleware(); ``` -Use class middleware for reusable code, dependencies, and easier unit tests. +Use class middleware for reusable code, complex pipeline behavior, stateful middleware, or easier +unit tests. Do not extract a class just to hide a small inline delegate. ## Short-circuit cache @@ -94,7 +106,9 @@ lambda.UseMiddleware(async (context, next) => }); ``` -Place short-circuit middleware after auth/validation and before handler. +Place short-circuit middleware after auth/validation and before handler. If cache policy grows +beyond simple Lambda pipeline glue, delegate policy decisions to an injected service and pass +request ids/keys/values, not the whole Lambda context. ## Error boundary pattern diff --git a/skills/minimal-lambda/references/repo-workflow.md b/skills/minimal-lambda/references/repo-workflow.md index 71a55805..15a06ccd 100644 --- a/skills/minimal-lambda/references/repo-workflow.md +++ b/skills/minimal-lambda/references/repo-workflow.md @@ -31,10 +31,13 @@ Tests: ```bash task test:all -# or +# or pick the target framework relevant to the change DOTNET_NOLOGO=1 dotnet test --configuration Release -f net10.0 ``` +Use `task test:all` when practical; focused `dotnet test -f ...` commands are shortcuts, not the +canonical full suite. + AOT check: ```bash diff --git a/skills/minimal-lambda/references/troubleshooting.md b/skills/minimal-lambda/references/troubleshooting.md index d0d4eb1f..35ea9553 100644 --- a/skills/minimal-lambda/references/troubleshooting.md +++ b/skills/minimal-lambda/references/troubleshooting.md @@ -15,7 +15,7 @@ Check: - project uses supported C# language version - package references include `MinimalLambda` -- `MapHandler` call shape is a static, analyzable delegate/method group +- `MapHandler` call shape is a static, analyzable inline delegate or method group - source generator diagnostics in build output ### Missing or duplicate `[FromEvent]`