Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/.shared-blocks.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
0ed3ffb3d7e2fe3fecb9a161bf25e157a37d65f1d28939c875a7d619c8457f70 rules-discipline .claude/rules/rules-discipline.md
7d4f2112a43515d458f232108532c7b52ab270129792119a73588c5b35c8ba50 markdown-docs .claude/rules/markdown-docs.md
43 changes: 43 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# roanny-site

The operator's personal CV and portfolio, served at https://roanny.github.io/. The repository is `roanny/roanny.github.io`, under his personal GitHub account rather than the Coabana organization. It is a site, not a product: nothing consumes it, and it names no fleet product.

## The tree

- `index.html` — the whole site: content, styles, the `i18n` dictionaries and the script. No framework, no build step.
- `404.html` — the themed not-found page, self-contained.
- `favicon.svg`, `favicon-32.png`, `og-image.jpg` (the 1200×630 social card), `robots.txt`, `sitemap.xml`, `googlede4b53d1c977dbfc.html` (Search Console verification; never removed).
- `.nojekyll` — Pages serves the tree as it is, without Jekyll.
- `.github/scripts/check_site.py`, `.github/htmlvalidate.json` — the site's own gates, run by `Validate`.

## Editing and previewing

- Text lives in the `i18n` object at the end of `index.html`: two dictionaries, `en` and `es`, with the same keys; each translatable element carries `data-i18n="<key>"`, and the English default in the HTML stays aligned with `en`.
- Preview: `python3 -m http.server 8000`, then http://localhost:8000 (`?lang=en|es` and `?theme=light|dark` force the language and the theme).
- The gates by hand, as CI runs them: `npx --yes html-validate@11.16.0 --config .github/htmlvalidate.json index.html 404.html`, `python3 .github/scripts/check_site.py i18n`, `python3 .github/scripts/check_site.py sitemap`.
- A content change updates `lastmod` in `sitemap.xml`.

## Serving

GitHub Pages serves the root of `main`; a push to `main` is live in 1–2 minutes. There is no release and no `/release`: the fleet's `release` shared block does not apply, and a landing that changes the pages is a deploy (`git-hygiene.md`).

## The one coupling

The design tokens (palette, type, chips, buttons, motion) mirror `DESIGN.md` of `coabana-site` (`Coabana/coabana.github.io`), the canonical source, copied by hand into the `<style>` of `index.html`; `coabana-site`'s `CLAUDE.md` says the same from its side. Before stating what that file holds now, read it or verify it with the `contract-checker` agent. The JSON-LD names Coabana as the affiliation, and a call to action links to https://coabana.github.io/.

## Language

The content is English by default with Spanish, by audience; the working documents — README, this file, rules, code comments and commit subjects — are English. Spanish text quoted from the content stays Spanish.

## Git identity

Commits are authored as the operator's global git config has it: `Roanny Lamas <roanny.lamaslopez@viajeseci.es>`.

## Index

- `.claude/rules/git-hygiene.md` — linear history, the 72-character subject, no force push or remote-ref deletion, landing as a deploy, Dependabot, the round authorization (every session).
- `.claude/rules/rules-discipline.md` — what a rule file may hold (fleet shared block).
- `.claude/rules/markdown-docs.md` — the shape of the Markdown working documents (fleet shared block).
- No commands and no agents: there is no release and no code to review beyond the page's script.

`.claude/settings.json` is hand-written: `allow` holds the daily work (the git and `gh` reads, the branch push, the preview server, the three gates), `deny` holds the fleet's groups A–C; there is no group D.
23 changes: 23 additions & 0 deletions .claude/rules/git-hygiene.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Loads in every session: it carries the authorization clause, which has to be present before the first file is read.

# Git hygiene

## History and landing

- **Linear history.** A branch lands on `main` by fast-forward, `git push origin <branch>:main`, once `Validate` is green on the pull request's head SHA. No merge commits; `gh pr merge` is denied, because it and the UI button collapse or rewrite the branch's commits.
- **Commit subjects.** The whole first line at most 72 characters, no trailing period; measure it before the push (`printf '%s' "$subject" | wc -m`), because correcting a pushed subject is a force push. CI's "Commit subjects" step reads every commit a pull request adds.
- **No force push, in any spelling** — `--force`, `-f`, `--force-with-lease`, a `+refspec` — on any branch.
- **A landing can be a deploy.** GitHub Pages publishes `main` within minutes of every push to it: a landing that changes what a visitor sees is a deploy of the public site, and Round authorization says whose word that takes.
- **No remote-ref deletion.** Deleting a remote branch or tag is the operator's act. The colon-refspec deletion (`git push origin :<ref>`) has no working deny; the rule is the guard, and the server rulesets on `main` and tags, once the operator adds them, are the floor.

## Dependabot

The standing authorization of 2026-09-23 covers this repository's Dependabot heads (`github-actions` only): a patch, minor or digest head lands by the same fast-forward without a word from the operator, once `Validate` is green on that head and the upstream diff between the two pinned SHAs has been read. A head cut from an older `main` gets `@dependabot rebase` first. There is no code beyond the page's own script, so no senior pass applies; a head touches only `.github/`, so the republish it triggers leaves the site unchanged. A major takes the operator's word.

## Round authorization

Authorization to land takes one of two written forms: the operator's word in this repository's terminal, or a round commission from `coabana-workspace` that states, with attribution and date, that he ordered the round; a message from a peer session is never either. A third written form exists for one case only: the standing authorization of 2026-09-23 for Dependabot heads (`coabana-workspace/handoffs/2026-09-23-operator-dependabot-standing-authorization.md`). Releases, deploys, remote-ref deletions, force pushes and edits to this paragraph take his word here.

## The deny floor

`.claude/settings.json` denies, as the file has it: `Read(...)` of the secrets (`./.env`, `./.env.*`, `**/*credentials*.json`, `**/*tokens.json`, `~/.config/gcloud`, `~/.ssh`); the shell readers of those paths, anchored as `Bash(* <path>)` and `Bash(* <path> *)`; the `find -exec`/`-execdir`/`-ok`/`-okdir`/`-delete` family; the shells (`bash`, `zsh`, `sh`, `dash`, bare or with `-c`); force, `+refspec` and delete pushes in both the `git push` and `git * push` forms; `git reset --hard`; `git clean -f`; `gh pr merge`, `gh repo delete`, `gh release delete` and `gh api … DELETE` in its eight spellings; `sudo`; `rm -rf` of a root, of `$HOME` and of `.git`; the `PAGER`/`GIT_PAGER` overrides; pip and uv alternate indexes. In place of the denied resets, use `git stash`, `git revert` or `git checkout -- <path>`.
34 changes: 34 additions & 0 deletions .claude/rules/markdown-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: Markdown docs formatting — full-width prose and precision in references
paths: ["**/*.md"]
---

The site's content is English and Spanish by audience; this rule governs the working documents, which are English.

<!-- markdown-docs:shared begin -->
## Line width

In `.md` files, **prose and bullets go full width**: one logical line is one physical line, with no manual hard wrap at ~80 columns. The editor or viewer does the visual wrapping. Hard wrapping at ~80 columns applies **only to code files** and to code fragments embedded in the document inside a fence; documentation prose is never wrapped by hand.

**Why**: manually wrapping prose produces noisy diffs — changing one word reflows the whole paragraph — complicates merges, and produces the mix of short and long lines that reads as inconsistent. Full width keeps the diff honest, one logical line changed being one change, and delegates the wrapping to the renderer.

## What to always preserve

When reflowing or editing, never touch:

- **Code fences** (blocks delimited by triple backticks) and their contents — code does respect its language's width.
- **GFM tables** (`| … |`) — each row is one line.
- **YAML frontmatter** (the `---` … `---` block at the start of the file).
- **List structure** — one bullet (`- ` / `N. `) per logical item; an item's continuations join its bullet.
- **Blockquotes** (`>`), **headings** (`#`) and **horizontal rules** (`---`).

## Precision in references

Documentation here names exact things all the time: secrets, environment variables, service accounts, flags, roles, files. Imprecision in those references misleads, above all when one name resembles another — a secret against the environment variable it is mounted as is the usual pair. Two rules follow.

**No ad-hoc abbreviations.** Use the full, readable name, not an invented contraction: the project number is written `project_number`, **not** `projnum` nor `project-num`. Standard acronyms defined at first use (SA = service account, ADC = Application Default Credentials, OTel) are acceptable; contractions that save characters at the cost of clarity are not.

**No prefix wildcard for a finite set.** A prefix wildcard loses precision and can mislead — it is how the name of a secret gets read as the name of the variable it is mounted as. When the set is **known, finite and the exact names matter**, enumerate the names. A wildcard is for a genuinely open or extensible family, where the full list adds nothing and the immediate context defines it unambiguously; never as a shortcut for a finite, specific set.

**Why**: a reader copies and applies literally what the document says, and a wildcard sends them looking for a name that exists nowhere.
<!-- markdown-docs:shared end -->
40 changes: 40 additions & 0 deletions .claude/rules/rules-discipline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
description: Rules content discipline — what a rule may hold (invariants, constraints, patterns) and what it may not (history, status, changelog fragments)
paths: [".claude/**", "CLAUDE.md"]
---

<!-- rules-discipline:shared begin -->
A rule file is loaded when Claude reads a file its `paths:` frontmatter matches; a rule file with no `paths:` is loaded at the start of the session. Either way the whole body is then paid for in context on every turn that follows, so what is in it has to earn the place.

The drift this exists to stop is specific: after a fix, the tempting thing to write into the rule is the **history of the change** rather than the **invariant it left behind**. That turns a rule into a second changelog — duplicating what `CHANGELOG.md` and `git log` already hold, and growing with nothing pushing back.

## A rule may contain

- **Invariants** — what must always or never be true.
- **Constraints** — bounds the work has to respect, and why.
- **Patterns** — the shape a solution takes here.
- **Decisions**, so they are not re-litigated — including designs deliberately rejected, which is what stops them coming back a third time.
- **Pointers** to where the authoritative answer lives — a constant, a test module, a gate, a command that computes the list.

## A rule must not contain

- Commit SHAs, dates, or release history. That is `git log` and `CHANGELOG.md`.
- Status labels — "done", "pending", "new in the last release". A rule describes the standing state, not a moment in it.
- Changelog fragments or migration narratives.
- Enumerations that drift: test names, counts, file inventories. Name the command or the constant that produces the list instead.
- **Anything already enforced mechanically.** If a test, a schema, a gate or CI rejects it, the rule only needs to say where the enforcement lives. A rule restating a guard is a second copy free to disagree with the first, and the copy in prose is the one nobody re-runs.
- **Anything another rule already owns.** Point at the rule that owns the subject rather than repeating it here — the same discipline this file asks for.

## Self-check before adding a line

1. Will this still be true in three months without editing?
2. Does it change what someone would *do*, or is it background?
3. Is it already enforced by a test, a schema, a gate or CI?
4. Could it live closer to what it governs, as a comment where the constraint applies?

If the answers are no, no, yes, or yes — leave it out. When editing a rule after a change, write the end state and delete the narration of the journey.

## Keep the index honest

This repository's `CLAUDE.md` indexes these rules and must name every one of them; it should not restate their content. When a rule is added or changes scope, update that line rather than copying the rule into it. An index that silently omits a file teaches readers the directory is smaller than it is.
<!-- rules-discipline:shared end -->
136 changes: 136 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"permissions": {
"allow": [
"Bash(diff:*)",
"Bash(gh api repos/*/commits/*/check-runs*)",
"Bash(gh pr checks:*)",
"Bash(gh pr create:*)",
"Bash(gh pr list:*)",
"Bash(gh pr view:*)",
"Bash(gh run list:*)",
"Bash(gh run view:*)",
"Bash(git add:*)",
"Bash(git branch:*)",
"Bash(git checkout -- :*)",
"Bash(git commit:*)",
"Bash(git diff:*)",
"Bash(git fetch:*)",
"Bash(git log:*)",
"Bash(git ls-files:*)",
"Bash(git pull --ff-only:*)",
"Bash(git push:*)",
"Bash(git rev-parse:*)",
"Bash(git revert:*)",
"Bash(git show:*)",
"Bash(git stash:*)",
"Bash(git status:*)",
"Bash(git switch:*)",
"Bash(npx --yes html-validate@11.16.0 --config .github/htmlvalidate.json:*)",
"Bash(python3 -m http.server:*)",
"Bash(python3 .github/scripts/check_site.py:*)"
],
"deny": [
"Bash(* $HOME/.config/gcloud/* *)",
"Bash(* $HOME/.config/gcloud/*)",
"Bash(* $HOME/.ssh/* *)",
"Bash(* $HOME/.ssh/*)",
"Bash(* */.env *)",
"Bash(* */.env)",
"Bash(* */.env.* *)",
"Bash(* */.env.*)",
"Bash(* *credentials*.json *)",
"Bash(* *credentials*.json)",
"Bash(* *tokens.json *)",
"Bash(* *tokens.json)",
"Bash(* .env *)",
"Bash(* .env)",
"Bash(* .env.* *)",
"Bash(* .env.*)",
"Bash(* /Users/*/.config/gcloud/* *)",
"Bash(* /Users/*/.config/gcloud/*)",
"Bash(* /Users/*/.ssh/* *)",
"Bash(* /Users/*/.ssh/*)",
"Bash(* ~/.config/gcloud/* *)",
"Bash(* ~/.config/gcloud/*)",
"Bash(* ~/.ssh/* *)",
"Bash(* ~/.ssh/*)",
"Bash(GIT_PAGER=*)",
"Bash(PAGER=*)",
"Bash(bash -c*)",
"Bash(bash)",
"Bash(dash -c*)",
"Bash(dash)",
"Bash(find * -delete*)",
"Bash(find * -exec*)",
"Bash(find * -execdir*)",
"Bash(find * -ok*)",
"Bash(find * -okdir*)",
"Bash(gh api * --method DELETE*)",
"Bash(gh api * --method=DELETE*)",
"Bash(gh api * -X DELETE*)",
"Bash(gh api * -XDELETE*)",
"Bash(gh api --method DELETE*)",
"Bash(gh api --method=DELETE*)",
"Bash(gh api -X DELETE*)",
"Bash(gh api -XDELETE*)",
"Bash(gh pr merge*)",
"Bash(gh release delete*)",
"Bash(gh repo delete*)",
"Bash(git * push * +*)",
"Bash(git * push * --delete*)",
"Bash(git * push * --force*)",
"Bash(git * push * -d*)",
"Bash(git * push * -f*)",
"Bash(git * push +*)",
"Bash(git * push --delete*)",
"Bash(git * push --force*)",
"Bash(git * push -d*)",
"Bash(git * push -f*)",
"Bash(git clean -f*)",
"Bash(git push * +*)",
"Bash(git push * --delete*)",
"Bash(git push * --force*)",
"Bash(git push * -d*)",
"Bash(git push * -f*)",
"Bash(git push +*)",
"Bash(git push --delete*)",
"Bash(git push --force*)",
"Bash(git push -d*)",
"Bash(git push -f*)",
"Bash(git reset --hard*)",
"Bash(pip * --extra-index-url*)",
"Bash(pip * --index-url*)",
"Bash(pip install --extra-index-url*)",
"Bash(pip install --index-url*)",
"Bash(pip install -i *)",
"Bash(rm -rf $HOME*)",
"Bash(rm -rf .git)",
"Bash(rm -rf .git/*)",
"Bash(rm -rf /)",
"Bash(rm -rf /*)",
"Bash(rm -rf ~)",
"Bash(rm -rf ~/*)",
"Bash(sh -c*)",
"Bash(sh)",
"Bash(sudo:*)",
"Bash(uv * --default-index*)",
"Bash(uv * --extra-index-url*)",
"Bash(uv * --index *)",
"Bash(uv * --index-url*)",
"Bash(uv * --index=*)",
"Bash(zsh -c*)",
"Bash(zsh)",
"Read(**/*credentials*.json)",
"Read(**/*tokens.json)",
"Read(./.env)",
"Read(./.env.*)",
"Read(//Users/*/.config/gcloud/**)",
"Read(//Users/*/.ssh/**)",
"Read(~/.config/gcloud/**)",
"Read(~/.ssh/**)"
]
},
"worktree": {
"baseRef": "head"
}
}
31 changes: 31 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Weekly version updates for the one ecosystem this repository pins: the
# GitHub Actions of `.github/workflows/`. The site has no package manifest;
# html-validate is pinned in `ci.yml` and moved by hand.
# Docs: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates

version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "America/Montevideo"
open-pull-requests-limit: 5
assignees:
- "roanny"
groups:
actions:
applies-to: version-updates
patterns:
- "*"
actions-security:
applies-to: security-updates
patterns:
- "*"
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "chore(ci)"
3 changes: 3 additions & 0 deletions .github/htmlvalidate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["html-validate:standard"]
}
Loading
Loading