diff options
| -rw-r--r-- | config.toml | 13 | ||||
| -rw-r--r-- | content/blog/2026-01-26-good-for-people-good-for-agents-documentation-and-uniform-tooling.md | 103 | ||||
| -rw-r--r-- | content/blog/_index.md | 8 | ||||
| -rw-r--r-- | static/css/style.css | 211 | ||||
| -rw-r--r-- | static/img/posts/robots.png | bin | 0 -> 8221 bytes | |||
| -rw-r--r-- | static/processed_images/robots.15debd353e9e6547.png | bin | 0 -> 10370 bytes | |||
| -rw-r--r-- | templates/base.html | 23 | ||||
| -rw-r--r-- | templates/blog.html | 18 | ||||
| -rw-r--r-- | templates/blog_post.html | 19 | ||||
| -rw-r--r-- | templates/header_macros.html | 19 | ||||
| -rw-r--r-- | templates/pagination_macros.html | 25 | ||||
| -rw-r--r-- | templates/post_macros.html | 42 | ||||
| -rw-r--r-- | templates/shortcodes/hero.html | 1 | ||||
| -rw-r--r-- | templates/shortcodes/window.html | 2 | ||||
| -rw-r--r-- | templates/taxonomy_list.html | 17 | ||||
| -rw-r--r-- | templates/taxonomy_single.html | 18 | ||||
| -rw-r--r-- | templates/text.html | 2 |
17 files changed, 485 insertions, 36 deletions
diff --git a/config.toml b/config.toml index 1d65c5a..94e693c 100644 --- a/config.toml +++ b/config.toml @@ -1,16 +1,21 @@ # The URL the site will be built for base_url = "https://www.holistic.services" +generate_feeds = true + +author = "Rubén Beltrán del Río" # Whether to automatically compile all Sass files in the sass directory compile_sass = false # Whether to build a search index to be used later on by a JavaScript library build_search_index = true +exclude_paginated_pages_in_sitemap = "all" -[markdown] -# Whether to do syntax highlighting -# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola -highlight_code = false +# Taxonomies for blog posts. +taxonomies = [ + {name = "categories", rss = true, paginate_by=10}, + {name = "tags", rss = true, paginate_by=10}, +] [extra] # Put all your custom variables here diff --git a/content/blog/2026-01-26-good-for-people-good-for-agents-documentation-and-uniform-tooling.md b/content/blog/2026-01-26-good-for-people-good-for-agents-documentation-and-uniform-tooling.md new file mode 100644 index 0000000..b863ed4 --- /dev/null +++ b/content/blog/2026-01-26-good-for-people-good-for-agents-documentation-and-uniform-tooling.md @@ -0,0 +1,103 @@ ++++ +title = "Good for People, Good for Agents. Uniform Tooling and Documentation Repositories." +description = "How a central documentation repository and uniform tooling can make work better for people and the agents they use." +[extra] +visual_aid_src = "/img/posts/robots.png" +visual_aid_alt = "Five rectangles with different patterns have rays pointing to a bigger rectangle that is being held by a robot on the left, and a happy person on the right" +[taxonomies] +tags = ["Best Practices", "Agents", "Documentation"] +categories = ["Software Engineering"] ++++ +Many practices that are helpful for teams to collaborate, are also helpful to +improve how we work with agents. In this post I’d like to share how having +a centralized record for decisions and documentation, and a uniform tool to +build, run, and test projects can help people and the agents they build with. +<!-- more --> + +{% window(title="Visual Aid.", classes="shrink") %} +{{ hero() }} +{% end %} + +## Practice I, a Shared Documentation and Decision Repository. + +### What It Is. + +A repository that stores a record of your decisions as a team, and the current text of your practices: naming conventions, tooling choices, code quality standards, etc. + +### Why It’s Good For People. + +Treating our practices and agreements like any other repository makes it come alive: Anyone can submit a merge request to change how we work, and the history and discussion around our decisions is easy to read back. + +This repository becomes the record for both *how we do* and *why we do*. It’s helpful when onboarding new teammates, as long term memory for retrospectives, and as a reminder of the way we agreed we’d like to work (See: [Internal Processes](https://wiki.r.bdr.sh/view/welcome-visitors/view/engineering-management/view/internal-processes) in the wiki) + +### How to Implement It. + +1. Create a repository. +2. Open a Merge Request that creates a “README.md” with a description of how to use it: How to make a proposal, how consensus will be reached. +3. Discuss, modify and agree. +4. Merge, and you have your first decision logged! + +## Practice II, Standardized Commands Across Projects. + +### What It Is. + +A `Makefile` you add to every repository, with a set of agreed on commands to do common tasks: build, run, test, benchmark, lint, etc. + +### Why It’s Good For People. + +At a certain level of complexity, it’s likely that your team will work with more than one project, sometimes with more than one technology. + +If this is the case, it’s a lot easier to remember “`make dev` runs the server” instead of `pnpm run dev` (or are we using `yarn`?), `uv run flask run` (or was it `poetry`?), `cargo run`, `flutter run`. + +### How to Implement It. + +1. Create an empty Makefile with common commands. Here’s some ideas: + - `prepare` installs dependencies. + - `build` builds the bundle / binary. + - `dev` runs the application in development/watch mode. + - `lint` runs the formatter and linter in check mode. + - `format` runs the formatter and linter in fix mode. + - `test` runs tests. + - `coverage` prints out a code coverage report. + - `benchmark` runs the performance benchmarking suite. + - `audit` runs dependency / license auditing. +2. Add the empty Makefile to your shared documentation repository and explain what each command should do. +3. Add the Makefile to each of your repositories, it’s likely to be a simple mapping to the existing language’s tooling. + +## Making them also good for Agents. + +Now that you have these tools, it’s very easy to make your agent aware of them. My documentation repository is called `readme.doc` , and I use Claude Code, so in the CLAUDE.md` for each repository, I add the following line: + +``` +Follow practices as described in @../readme.doc/CLAUDE.md +``` + + +This `CLAUDE.md` in the repository is a “machine entry point” for documentation, and looks like this: + +``` +Read @project-build-and-run-guidelines.md to understand how to build, lint, test, and run local tasks. +Before writing any code read @code-quality-guidelines.md +Before interacting with services, read @service-url-naming-guidelines.md +Before creating any new repo read @repository-naming-guidelines +``` + +This will give the agent a better starting point, and will output code that is more in line with my coding standards. For example, I don’t like that Claude often uses abbreviations for variable names, or that it tends to always add code, so my code quality guidelines will include entries like this: + +``` +- Don't use abbreviations for variables, eg. prefer coordinates to coords. +- Start by writing tests, and validate it worked when the tests pass. +- Use code that's idiomatic for the language you're writing for. +- Consider subtractive improvements. Consider how removing code can solve the problem before adding. +- Avoid very large functions, break into pieces with a single responsibility. +- Avoid generic module names like "utils", each module should have a specific concern. +- Prefer keeping generic functionality at the core, with specific logic at the leaves. +``` + +And that’s it. The code quality will be better, and by allowing calls to `make`, +easier to run without interruptions. + +You can read more about these practices in the wiki pages for +[Central Documentation Repository](https://wiki.r.bdr.sh/view/central-documentation-repository), +and [Standardized Makefile](https://wiki.r.bdr.sh/view/standardized-makefile) +where I'll continue to build up the concept and connect it with the patterns it enables. diff --git a/content/blog/_index.md b/content/blog/_index.md new file mode 100644 index 0000000..4082cb6 --- /dev/null +++ b/content/blog/_index.md @@ -0,0 +1,8 @@ ++++ +title = "Blog" +description = "Blog about Engineering and Leadership at Holistic Services, written by Rubén Beltrán del Río." +template = "blog.html" +sort_by = "date" +page_template = "blog_post.html" +paginate_by = 10 ++++ diff --git a/static/css/style.css b/static/css/style.css index 7bbb584..fadf388 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -53,14 +53,17 @@ /* Theme */ :root { - --primary-color: #0000f5; + --brand-color: #0000f5; --background: #fff; --foreground: #000019; --foreground-emphasis: #191919; + --foreground-secondary: #0F261F; --selection-color: #ccc; + --code-background: #DAE6E3; --font-display: 'Libre Caslon Condensed', 'Fallback Display', serif; --font-body: 'Libre Caslon', 'Fallback Body', serif; --font-windows: 'Helvetica Neue', Helvetica, Arial, sans-serif; + --font-pre: 'Input Mono Comp Ex Lt', 'Input Mono Compressed', monospace; /* There's an issue with Helvetica Neue Bold's glyph sizing that wrecks the grid. */ --font-windows-bold: Helvetica, Arial, sans-serif; --root-font-size: 1rem; @@ -73,7 +76,7 @@ ::selection { background-color: var(--selection-color); -webkit-text-stroke: 0; - color: var(--primary-color); + color: var(--brand-color); } /* CSS Reset */ @@ -87,6 +90,7 @@ li,p { text-align: justify; transform: translateY(7px); } +a { color: var(--brand-color)} strong { font-family: var(--font-body); color: var(--foreground-emphasis); @@ -134,7 +138,7 @@ body { font-synthesis: none; font-variant-emoji: text; color: var(--foreground); - background: var(--primary-color); + background: var(--brand-color); font-size: var(--root-font-size); line-height: var(--base-grid); display: flex; @@ -148,7 +152,7 @@ body { & > header { color: #fff; display: flex; - background: var(--primary-color); + background: var(--brand-color); font-family: var(--font-display); padding: var(--base-grid); position: relative; @@ -209,7 +213,7 @@ body { /* Animation */ canvas { - background: var(--primary-color); + background: var(--brand-color); display: block; height: 75px; width: 75px; @@ -219,6 +223,29 @@ body { width: 50px; } } + + &.small { + canvas { + height: 2lh; + width: 2lh; + } + p { + font-size: var(--root-font-size); + white-space: normal; + width: auto; + line-height: var(--line-height); + margin: 0 0 0 1lh; + text-align: left; + transform: none; + width: 4lh; + } + a { + margin: 0; + transform: none; + width: auto; + word-spacing: inherit; + } + } } } /* The Main Body */ @@ -242,7 +269,8 @@ main { h3 { font-size: 1.25rem; line-height: 1.25; - font-weight: 500; + font-family: var(--font-display); + font-weight: 300; transform: translateY(2px); margin: 2lh 0 1lh; } @@ -259,19 +287,23 @@ main { list-style: none; margin-left: 0; margin-bottom: var(--base-grid); - } - li { - position: relative; - padding-left: var(--base-grid); - - &::before { - content: "✳\FE0E"; - width: var(--base-grid); - display: inline-block; - margin-left: calc(-1 * var(--base-grid)); - text-align: left; + li { + position: relative; + padding-left: var(--base-grid); + + &::before { + content: "✳\FE0E"; + width: var(--base-grid); + display: inline-block; + margin-left: calc(-1 * var(--base-grid)); + text-align: left; + } } } + + ol { + margin: var(--base-grid) 0 var(--base-grid) var(--base-grid); + } } #introduction { @@ -357,9 +389,142 @@ footer { } } -#text { +.single-column { max-width: calc(35 * var(--base-grid)); + width: 100%; padding: var(--base-grid); + + h1 { + font-family: var(--font-display); + line-height: calc(2 * var(--base-grid)); + margin-bottom: calc(2 * var(--base-grid)); + transform: translateY(13px); + + + hr { + margin-top: calc(-1 * var(--base-grid) + 1px); + + + aside { + margin-bottom: 2lh; + } + } + } + h2 { transform: translateY(4px) } + h3 { transform: translateY(6px) } + h2, h3, h4 { + line-height: var(--base-grid); + margin: var(--base-grid) 0; + } + p { + margin: var(--base-grid) 0; + &:empty { display: none } + } + + h1,h2,h3,h4 { + a { + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + } + + li code, p code { + line-height: 1; + padding: 0 0.25rem + } + code, pre { + background-color: var(--code-background); + overflow-x: scroll; + scrollbar-width: none; + border-radius: var(--radius); + font-family: var(--font-pre); + font-weight: 100; + } + pre { + padding: var(--base-grid); + } + + hr { + height: 1px; + border: 0; + background-color: var(--foreground); + margin-top: calc(var(--base-grid) - 1px); + } + + .meta { + font-style: italic; + display: flex; + color: var(--foreground-secondary); + justify-content: space-between; + font-size: 0.8125rem; + gap: 1lh; + line-height: var(--base-grid); + transform: translateY(7px); + + @media (max-width: 500px) { + flex-direction: column; + gap: 0; + } + + time { + white-space: nowrap; + } + + .category { flex-shrink: 0 } + .tags { + flex-shrink: 1; + text-align: right; + menu { + display: inline; + list-style: none; + li { + display: inline; + margin: 0 0.25rem; + } + } + @media (max-width: 500px) { + text-align: left; + } + } + } + + &.taxonomy-index, &.blog-index { + p { + margin-bottom: 2lh; + } + } + + .pager { + display: flex; + justify-content: space-between; + + .older, .newer { + max-width: 50%; + } + + .older:only-child { + margin-left: auto; + } + } + &.blog-post .pager { margin-top: 2lh } + + .hero-summary { + align-items: center; + display: flex; + gap: 1lh; + margin: 1lh 0; + + p { + margin: 0; + } + img { + border: 1px solid var(--foreground); + border-radius: var(--radius); + display: block; + height: min-content; + width: min-content; + } + } } /* The Floating Window */ @@ -373,6 +538,11 @@ footer { font-family: var(--font-windows); + &.shrink { + width: min-content; + margin: 0 auto; + } + .titlebar { border-bottom: var(--border-width) solid #000; font-family: var(--font-windows-bold); @@ -406,6 +576,11 @@ footer { .content { padding: calc(var(--base-grid) - 1px) var(--base-grid); + + > img:only-child { + margin: 0 auto; + display: block; + } } hr.divider { diff --git a/static/img/posts/robots.png b/static/img/posts/robots.png Binary files differnew file mode 100644 index 0000000..380fab6 --- /dev/null +++ b/static/img/posts/robots.png diff --git a/static/processed_images/robots.15debd353e9e6547.png b/static/processed_images/robots.15debd353e9e6547.png Binary files differnew file mode 100644 index 0000000..c10650f --- /dev/null +++ b/static/processed_images/robots.15debd353e9e6547.png diff --git a/templates/base.html b/templates/base.html index 5cc8b33..62629ff 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,3 +1,5 @@ +{% import "header_macros.html" as header_macros %} + <!DOCTYPE HTML> <html lang="en"> @@ -5,11 +7,11 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="author" content="Rubén Beltrán del Río"> - <meta name="description" content="{{ section.description }}"> + <meta name="description" content="{{ config.description }}"> <meta name="theme-color" content="#0000f5"> <meta name="fediverse:creator" content="@ruben@friendship.quest" /> - <title>Rubén Beltrán del Río — {{ section.title }}</title> + <title>Rubén Beltrán del Río — {{ config.title }}</title> <link rel="canonical" href="{{current_url | safe}}"> @@ -19,22 +21,19 @@ <link rel="preload" href="/font/LibreCaslonText-Italic[wght].woff2" as="font" type="font/woff2" crossorigin> <link rel="stylesheet" type="text/css" href="/css/style.css"> + <link href="//fastly-cloud.typenetwork.com/projects/8926/fontface.css?6977e213" rel="stylesheet" type="text/css"> <link rel="author" href="humans.txt"> <link rel="icon" href="/img/favicon.png" /> + <link rel="alternate" href="/atom.xml" hreflang="en" title="Atom feed, all posts."> + <script type="module" src="/js/animation.js"></script> </head> <body> - <header> - <h1> - <canvas width="75" height="75" aria-hidden="true"></canvas> - <a href="/">Rubén Beltrán del Río</a> - </h1> - <p>✷ Holistic - tech - services ✷</p> - </header> + {% block header %} + {{ header_macros::main_header() }} + {% endblock %} <main> {% block content %}{% endblock %} </main> @@ -43,10 +42,10 @@ <ul> <li><a href="/impressum">Impressum</a></li> <li><a href="/privacy-policy">Privacy Policy</a></li> + <li><a href="/blog">Blog</a></li> <li>Check out <a href="https://r.bdr.sh">my personal website.</a></li> </ul> </menu> </footer> </body> </html> - diff --git a/templates/blog.html b/templates/blog.html new file mode 100644 index 0000000..cd3c104 --- /dev/null +++ b/templates/blog.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% import "post_macros.html" as post_macros %} +{% import "header_macros.html" as header_macros %} +{% import "pagination_macros.html" as pagination_macros %} + +{% block header %} + {{ header_macros::small_header() }} +{% endblock %} + +{% block content %} + <section class="blog-index single-column"> + <h1>Articles.</h1> + {% for page in paginator.pages %} + {{ post_macros::summary(post=page) }} + {% endfor %} + {{ pagination_macros::index_pages() }} + </section> +{% endblock %} diff --git a/templates/blog_post.html b/templates/blog_post.html new file mode 100644 index 0000000..82850c6 --- /dev/null +++ b/templates/blog_post.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% import "header_macros.html" as header_macros %} +{% import "post_macros.html" as post_macros %} +{% import "pagination_macros.html" as pagination_macros %} + +{% block header %} + {{ header_macros::small_header() }} +{% endblock %} + +{% block content %} +<article class="blog-post single-column"> + <h1> + <a href="{{ page.permalink }}">{{ page.title }}</a> + </h1> + {{ post_macros::meta(post=page) }} + {{ page.content | safe }} + {{ pagination_macros::page_pages() }} +</article> +{% endblock %} diff --git a/templates/header_macros.html b/templates/header_macros.html new file mode 100644 index 0000000..efe286c --- /dev/null +++ b/templates/header_macros.html @@ -0,0 +1,19 @@ +{% macro main_header() %} + <header> + <h1> + <canvas width="75" height="75" aria-hidden="true"></canvas> + <a href="/">Rubén Beltrán del Río</a> + </h1> + <p>✷ Holistic + tech + services ✷</p> + </header> +{% endmacro post_summary %} + +{% macro small_header() %} + <header class="small"> + <canvas width="75" height="75" aria-hidden="true"></canvas> + <p><a href="/">✷ Holistic tech services ✷</a></p> + </header> +{% endmacro post_summary %} + diff --git a/templates/pagination_macros.html b/templates/pagination_macros.html new file mode 100644 index 0000000..89e8c98 --- /dev/null +++ b/templates/pagination_macros.html @@ -0,0 +1,25 @@ +{% macro index_pages() %} + <nav class="pager"> + {% if paginator.next or paginator.previous%} + {% if paginator.previous %} + <article class="newer"><a href="{{ paginator.previous }}"> 〈〈 Newer</a></article> + {% endif %} + {% if paginator.next %} + <article class="older"><a href="{{ paginator.next }}"> Older 〉〉</a></article> + {% endif %} + {% endif %} + </nav> +{% endmacro index_pages %} + +{% macro page_pages() %} + <nav class="pager"> + {% if page.lower or page.higher %} + {% if page.lower %} + <article class="newer"><a href="{{ page.lower.permalink }}"> 〈〈 {{ page.lower.title }}</a></article> + {% endif %} + {% if page.higher %} + <article class="older"><a href="{{ page.higher.permalink }}"> {{ page.higher.title }} 〉〉</a></article> + {% endif %} + {% endif %} + </nav> +{% endmacro page_pages %} diff --git a/templates/post_macros.html b/templates/post_macros.html new file mode 100644 index 0000000..c128bc5 --- /dev/null +++ b/templates/post_macros.html @@ -0,0 +1,42 @@ +{% macro meta(post) %} + <hr> + <aside class="meta"> + <time datetime="{{ post.date }}">{{ post.date | date(format="%e %B %Y") }}</time> + {% if post.taxonomies.categories %} + <article class="category"> + § + {% for category in post.taxonomies.categories %} + <a href="{{ get_taxonomy_url(kind="categories", name=category) }}">{{ category }}</a> + {% endfor %} + </article> + {% endif %} + {% if post.taxonomies.categories %} + <article class="tags"> + ∈ + <menu> + {% for tag in post.taxonomies.tags %} + <li><a href="{{ get_taxonomy_url(kind="tags", name=tag) }}">{{ tag }}</a></li> + {% endfor %} + </menu> + </article> + {% endif %} + </aside> +{% endmacro meta %} + +{% macro summary(post) %} +<article class="post-summary"> + <h2> + <a href="{{ post.permalink }}">{{ post.title }}</a> + </h2> + {{ post_macros::meta(post=post) }} + {% if post.extra.visual_aid_src and post.extra.visual_aid_alt %} + <section class="hero-summary"> + {% set resized_image = resize_image(path=post.extra.visual_aid_src, width=100, height=75) %} + <img src="{{ resized_image.url }}" alt="{{post.extra.visual_aid_alt}}" title="{{post.extra.visual_aid_alt}}"> + <p>{{ post.summary | safe }}</p> + </section> + {% else %} + <p>{{ post.summary | safe }}</p> + {% endif %} +</article> +{% endmacro summary %} diff --git a/templates/shortcodes/hero.html b/templates/shortcodes/hero.html new file mode 100644 index 0000000..09c6d6f --- /dev/null +++ b/templates/shortcodes/hero.html @@ -0,0 +1 @@ +<img title="{{ page.extra.visual_aid_alt }}" alt="{{ page.extra.visual_aid_alt }}" src="{{ page.extra.visual_aid_src }}"> diff --git a/templates/shortcodes/window.html b/templates/shortcodes/window.html index 3fe8b50..6fe4a90 100644 --- a/templates/shortcodes/window.html +++ b/templates/shortcodes/window.html @@ -1,4 +1,4 @@ -<section class="window cutoff-corners"> +<section class="window cutoff-corners {% if classes %}{{classes}}{% endif %}"> <header class="titlebar"> <span class="label">{{title}}</span> </header> diff --git a/templates/taxonomy_list.html b/templates/taxonomy_list.html new file mode 100644 index 0000000..18e53ce --- /dev/null +++ b/templates/taxonomy_list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% import "header_macros.html" as header_macros %} + +{% block header %} + {{ header_macros::small_header() }} +{% endblock %} + +{% block content %} +<section class="taxonomy single-column"> + <h1>{{ taxonomy.name | title }}</h1> + <ul> + {% for term in terms %} + <li><a href="{{ term.permalink }}">{{ term.name }}</a></li> + {% endfor %} + </ul> +</section> +{% endblock %} diff --git a/templates/taxonomy_single.html b/templates/taxonomy_single.html new file mode 100644 index 0000000..e90c968 --- /dev/null +++ b/templates/taxonomy_single.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% import "header_macros.html" as header_macros %} +{% import "post_macros.html" as post_macros %} +{% import "pagination_macros.html" as pagination_macros %} + +{% block header %} + {{ header_macros::small_header() }} +{% endblock %} + +{% block content %} +<section class="taxonomy-index single-column"> + <h1><a href="/{{taxonomy.name | slugify}}">{{ taxonomy.name | title }}</a> / {{ term.name }}</h1> + {% for page in paginator.pages %} + {{ post_macros::summary(post=page) }} + {% endfor %} + {{ pagination_macros::index_pages() }} +</section> +{% endblock %} diff --git a/templates/text.html b/templates/text.html index 9ba2a71..0ca19f4 100644 --- a/templates/text.html +++ b/templates/text.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} -<section id="text"> +<section class="text single-column"> {{ section.content | safe }} </section> |