commit 446e2190140e963353c7b2036a7efdf9e1393f19 Author: Klagarge Date: Thu Mar 19 21:05:44 2026 +0100 chore: init diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..1f72a9c --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,85 @@ +name: Build PDF & Release +run-name: Build PDF and Release ${{ github.ref_name }} + +on: + push: + branches: + - main + paths: + - '**.md' + - '.github/workflows/**' + - 'md-pdf.ron' + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Rust cache + uses: swatinem/rust-cache@v2 + + - name: Install Typst + run: | + mkdir -p /usr/local/bin + curl -L -o typst.tar.xz https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz + tar -xJf typst.tar.xz --strip-components=1 -C /usr/local/bin/ typst-x86_64-unknown-linux-musl/typst + typst --version + + - name: Install md-pdf (Rust) + run: | + cargo install md-pdf + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Install svu (Go) + run: go install github.com/caarlos0/svu@latest + + - name: Build PDF + run: | + md-pdf README.md + mv README.pdf PIS.pdf + + # Calculate version based on commit messages (fix, feat, breaking) + - name: Calculate Next Version + id: version + run: | + NEW_TAG=$(svu next) + echo "tag=$NEW_TAG" >> $GITHUB_OUTPUT + echo "Next version : $NEW_TAG" + + - name: Push Tag + run: | + git config user.name "Gitea Actions" + git config user.email "actions@gitea.local" + git tag ${{ steps.version.outputs.tag }} + git push origin ${{ steps.version.outputs.tag }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Create release and upload PDF + # Note: softprops works very well on recent Gitea + - name: Create Release + uses: softprops/action-gh-release@v1 + if: ${{ steps.version.outputs.tag != '' }} # Safety check + with: + tag_name: ${{ steps.version.outputs.tag }} + name: Release ${{ steps.version.outputs.tag }} + files: PIS.pdf + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f99e88 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +--- +title: "GULAG" +subtitle: "Guidelines & Unified Laws for Advanced Gitflow" +--- + +# General +- Git history **has** to be kept linear. Branches **have** to be __rebased__ before being merged. +- Merge commit is stricly forbidden. +- Force pushes are disallowed on shared branches unless discussed beforehand with affected people. + +# Branches +Branch names *should* be prefixed by a type and the scope. For example: +- `feat/nodes/sleep-mode`: implements a new feature +- `fix/gateway/lost-messages`: fixes a bug or issue +- `refactor/db/endpoints`: modifies the code structure without adding new features + + + +# Pull Requests +- Branches **must** be merged into the main branch through a pull request. +- Pull requests *should* be kept relatively short. +- They **must** be reviewed by at least one other person. +- They **must** have a meaningful title, summarizing the features or fixes it contains, and can have a more in-depth description to help reviewers understand the contents +- If a pull request is related to an existing issue or task, its description *should* contain its reference. For example: + +``` +fix(gateway): lost messages + +This PR fixes the dropping of incoming messages in the gateway during high-traffic bursts before they can be published to the MQTT broker. + +The internal buffer was too small (size 5). A slight increase to 15 solves the issue. + +This PR also adds logging when the limits of 10 and 15 messages are reached, to help understand the root cause if the problem happens again. + +Fixes: #42 +``` + +A branch that has been merged **must** be deleted. + +# Commits +## Content +- A commit *should* be an atomic change targeting only 1 specific subject. +- Build artifacts, cache directories, large files, personal configuration, environment variables and secrets **must** not be committed. + If possible, such files *should* be added to `.gitignore` to avoid accidentally committing them. +- External libraries, especially large packages, *should* either not be included in the repository (with installation steps), or integrated as Git [submodules](https://github.blog/open-source/git/working-with-submodules/). + +## Message +Commit messages **must** follow the [conventional commit format](https://www.conventionalcommits.org/en/v1.0.0/). +In short, commit messages **must** be structured as follows: + +``` +(scope): + +[optional body] + +[optional footer(s)] +``` + +The type element describes the content of the commit. The following values are accepted: +- `fix`: patches a bug / issue +- `feat`: introduces a new feature +- `doc` or `docs`: only affects documentation +- `chore`: small changes to the repository which do not directly affect code or documentation +- `ci`: modifies CI/CD workflow configuration +- `refactor`: modifies existing code without changing the results / features + +Some other types can be accepted but the above list should suffice in most cases. +The description (or subject) should be kept short. +An optional body can be added to provide more details. +It *should* be written in the imperative present tense: +- ✅️ add database schema +- ❌ changed path resolution +- ❌️ fixes rounding error + +The end of the body can contain special keywords to indicate that it completes a specific issue or task, for example: +- Closes #42 +- Fixes #4 +- Resolves #38 + +## Authors +In cases where a commit is written by multiple people, footers **must** be added to indicate co-authors: + +``` +feat(nodes): implement sleep mode + +Co-Authored-By: John Doe +``` + +If a commit is partly written by an LLM, a footer tag `Assisted-by: AGENT_NAME:MODEL_VERSION` **must** be added, for example: + +``` +refactor(db): simplify endpoints routing + +Assisted-by: Claude:claude-3-opus +``` + + +# Credits +These guidelines are largely based on the [GitHub policy of the Duckify project](https://toys-r-us-rex.github.io/Duckify/architecture/github_policy.pdf) diff --git a/darko.typ b/darko.typ new file mode 100644 index 0000000..7d837a8 --- /dev/null +++ b/darko.typ @@ -0,0 +1,381 @@ +// Darko Template +#import "@preview/cmarker:0.1.8" +#import "@preview/mitex:0.2.6": mitex + +// Get system inputs +#let filepath = sys.inputs.at("filepath", default: "input.md") +#let language = sys.inputs.at("language", default: "en") +#let show-toc = sys.inputs.at("toc", default: "false") == "true" + +// Front matter inputs +#let has-frontmatter = sys.inputs.at("has_frontmatter", default: "false") == "true" +#let fm-title = sys.inputs.at("fm_title", default: none) +#let fm-subtitle = sys.inputs.at("fm_subtitle", default: none) +#let fm-author = sys.inputs.at("fm_author", default: none) +#let fm-date = sys.inputs.at("fm_date", default: none) +#let fm-tags = sys.inputs.at("fm_tags", default: none) +#let fm-version = sys.inputs.at("fm_version", default: none) +#let fm-logo = sys.inputs.at("logo", default: none) +#let fm-participants = sys.inputs.at("participants", default: none) + +// Dieter Rams color palette for dark theme +#let rams-white = rgb("f7f8f6ff") // Text color (whitish) +#let rams-light-grey = rgb("d9d2c6ff") // Light accents +#let rams-dark-grey = rgb("4a4a4aff") // Medium elements +#let rams-black = rgb("1f1f1fff") // Background (dark) +#let rams-green = rgb("736b1eff") // Accent color +#let rams-brown = rgb("8b7355ff") // Secondary accent +#let rams-red = rgb("ed3f1cff") // Highlight color +#let rams-orange = rgb("ed8008ff") // Warning/emphasis color + +// Parse tags from comma-separated string +#let tags-list = if fm-tags != none { fm-tags.split(",") } else { () } +#let participants-list = if fm-participants != none { fm-participants.split(",") } else { () } + +// Extract filename from filepath (remove path and .md extension) +#let filename = { + let path-parts = filepath.split("/") + let file = path-parts.last() + if file.ends-with(".md") { + file.slice(0, file.len() - 3) + } else if file.ends-with(".temp.md") { + file.slice(0, file.len() - 8) + } else { + file + } +} + +// Use front matter data or defaults +#let document-author = if fm-author != none { fm-author } else { none } +#let document-title = if fm-title != none { fm-title } else { filename } +#let document-subtitle = if fm-subtitle != none { fm-subtitle } else { none } + +// Parse date +#let document-date = if fm-date != none { + let date-str = fm-date + if date-str.len() == 10 and date-str.contains("-") { + let parts = date-str.split("-") + if parts.len() == 3 { + datetime(year: int(parts.at(0)), month: int(parts.at(1)), day: int(parts.at(2))) + } else { + datetime.today() + } + } else { + datetime.today() + } +} else { + datetime.today() +} + +// Set document properties +#set document( + author: if document-author != none {document-author} else {""}, + title: document-title, + keywords: if fm-tags != none { (if document-author != none {document-author} else {""}, if document-title != none {document-title} else {""}, "md-pdf", ..tags-list) } else { (if document-author != none {document-author} else {""}, if document-title != none {document-title} else {""}, "md-pdf") }, + date: document-date +) + +// Basic properties - dark background +#set page( + margin: (top: 3cm, bottom: 3cm, left: 3cm, right: 2.5cm), + fill: rams-black +) + +// Header and footer +#set page( + header: context(if here().page() >= 2 [ + #set text(9pt, fill: rams-light-grey) + #table( + columns: (80%, 20%), + stroke: none, + inset: -0.5em, + align: (x, y) => (left+bottom, right+top).at(x), + [#smallcaps[#document-title] #if document-subtitle != none {[| #smallcaps[#document-subtitle] ]}], + [#if fm-logo != none {[#v(1.2cm)#image(fm-logo,width:2cm)]}] + ) + #if fm-logo != none {[ + #line(start: (-0.5em, 0cm), length: 85%, stroke: (paint: rams-dark-grey, thickness: 0.5pt)) + ]} else {[ + #line(start: (-0.5em, 0cm), length: 101%, stroke: (paint: rams-dark-grey, thickness: 0.5pt)) + ]} + ]), + footer: context(if here().page() >= 2 [ + #line(length: 100%, stroke: (paint: rams-dark-grey, thickness: 0.5pt)) + #set text(9pt, fill: rams-light-grey) + #v(0.2em) + #if document-author != none {[#document-author #h(1fr)]} #document-date.display() #h(1fr) #context counter(page).display("1 / 1", both: true) + ]) +) + +// Font & language - white text on dark background +#set text( + font: ("Libertinus Serif", "Liberation Serif"), + fallback: true, + lang: language, + size: 11pt, + fill: rams-white +) + +// Heading styling +#show heading: set block(above: 1.2em, below: 1.2em) +#set heading(numbering: "1.1") + +#show heading.where(level: 1): it => { + set text(size: 18pt, weight: "bold", fill: rams-white) + set block(above: 1.2em, below: 1.2em) + if it.numbering != none { + let num = numbering(it.numbering, ..counter(heading).at(it.location())) + block( + fill: rams-green.darken(20%), + inset: (x: 12pt, y: 8pt), + radius: 4pt, + width: 100%, + stroke: 1pt + rams-green + )[ + #text(weight: "bold", fill: rams-white)[#num] #h(8pt) #it.body + ] + } else { + block( + fill: rams-green.darken(20%), + inset: (x: 12pt, y: 8pt), + radius: 4pt, + width: 100%, + stroke: 1pt + rams-green + )[ + #it.body + ] + } +} + +#show heading.where(level: 2): it => { + set text(size: 15pt, weight: "bold", fill: rams-orange) + set block(above: 1.3em, below: 0.9em) + if it.numbering != none { + let num = numbering(it.numbering, ..counter(heading).at(it.location())) + [#text(weight: "bold", fill: rams-orange)[#num] #h(6pt) #it.body] + } else { + [#it.body] + } + v(-7pt) + line(length: 70%, stroke: 2pt + rams-orange.lighten(20%)) +} + +#show heading.where(level: 3): it => { + set text(size: 13pt, weight: "semibold", fill: rams-brown) + if it.numbering != none { + let num = numbering(it.numbering, ..counter(heading).at(it.location())) + [#text(weight: "semibold", fill: rams-brown)[#num] #h(4pt) #it.body] + } else { + [#it.body] + } + v(-6pt) + line(length: 50%, stroke: 1pt + rams-brown.lighten(30%)) +} + +// Link color +#show link: it => text(fill: rams-green.lighten(20%), weight: "medium", it) + +// Code blocks +#show raw: set text( + font: ("Fira Code", "DejaVu Sans Mono"), + fallback: true +) +#show raw.where(block: false): it => { + box( + fill: rams-dark-grey.darken(40%), + inset: (x: 4pt, y: 2pt), + radius: 3pt, + stroke: 0.5pt + rams-dark-grey.darken(10%) + )[ + #text(fill: rams-light-grey, weight: "medium", size: 9pt)[#it] + ] +} +#show raw.where(block: true): it => { + set text(size: 9pt, fill: rams-light-grey) + block( + fill: rams-dark-grey.darken(40%), + width: 100%, + inset: 12pt, + radius: 5pt, + stroke: 1pt + rams-dark-grey.darken(10%) + )[ + #it + ] +} + +// Lists +#set list(indent: 1em, marker: ([•], [◦], [▪])) +#set enum(indent: 1em) +#show list: it => { + set text(fill: rams-white) + it +} +#show enum: it => { + set text(fill: rams-white) + it +} + +// Emphasis +#show emph: set text(style: "italic", fill: rams-light-grey, weight: "medium") +#show strong: set text(weight: "bold", fill: rams-green.lighten(10%)) + +// Quotes +#show quote: it => { + set text(fill: rams-light-grey, style: "italic") + block( + fill: rams-dark-grey.lighten(5%), + inset: (left: 12pt, rest: 10pt), + radius: 4pt, + stroke: (left: 3pt + rams-orange, rest: 1pt + rams-dark-grey.lighten(20%)) + )[#it] +} + +// Table styling +#show table: it => { + set text(size: 10pt, fill: rams-white) + set table( + stroke: 1pt + rams-dark-grey.lighten(30%), + fill: rams-dark-grey.lighten(10%) + ) + it +} + +// Figure captions +#set figure(numbering: "1", supplement: [Figure]) +#set figure.caption(separator: " — ") +#show figure.caption: it => { + set text(size: 9pt, style: "italic", fill: rams-light-grey) + block( + fill: rams-dark-grey.lighten(10%), + inset: 8pt, + width: 100%, + radius: 3pt, + stroke: 1pt + rams-dark-grey.lighten(30%) + )[ + #it + ] +} + +// Badge function with dark theme colors +#let badge(content, index: 0) = { + let colors = (rams-green, rams-brown, rams-orange, rams-red) + let color = colors.at(calc.rem(index, colors.len())) + box( + inset: (x: 6pt, y: 3pt), + radius: 3pt, + fill: color.darken(30%), + stroke: (paint: color, thickness: 0.5pt) + )[ + #text(weight: "medium", size: 8pt, fill: rams-white)[#content] + ] +} + +// Show basic document metadata if front matter exists +#if has-frontmatter [ + #v(1em) + // title + #if fm-title != none [ + #align(center)[ + #text(size: 22pt, weight: "bold", fill: rams-white)[#fm-title] + ] + #v(0.5em) + ] + // subtitle + #if fm-subtitle != none [ + #align(center)[ + #text(size: 15pt, style: "italic", fill: rams-light-grey)[#fm-subtitle] + ] + #v(0.8em) + ] + // logo + #if fm-logo != none {[#figure(image(fm-logo,width:4cm))]} + // metadata (author, date, version) + #let metadata = () + #if document-author != none { metadata.push(document-author) } + #if document-date != none { metadata.push(document-date.display()) } + #if fm-version != none { metadata.push(fm-version) } + + #if metadata.len() > 0 [ + #set text(11pt, fill: rams-light-grey) + #align(center)[ + #for (i, data) in metadata.enumerate() [ + #data + #if i < metadata.len() - 1 [ • ] + ] + ] + #v(0.8em) + ] + + #if fm-tags != none and tags-list.len() > 0 or fm-participants != none and participants-list.len() > 0 [ + #align(center)[#line(length: 90%, stroke: 1pt + rams-light-grey)] + ] + // tags + #if fm-tags != none and tags-list.len() > 0 [ + #table( + columns: (20%, 80%), + align: (right+horizon, left+horizon), + stroke: none, + [#smallcaps[#text(fill: rams-light-grey)[ + #if language == "de" {[ + Tags + ]} else if language == "fr" {[ + Balises + ]} else {[ + Tags + ]} + ]]], + [ + #for (i, tag) in tags-list.enumerate() [ + #badge(tag.trim(), index: i) + ] + ] + ) + ] + // participants + #if fm-participants != none and participants-list.len() > 0 [ + #table( + columns: (20%, 80%), + align: (right+horizon, left+horizon), + stroke: none, + [#smallcaps[#text(fill: rams-light-grey)[ + #if language == "de" {[ + Teilnehmer + ]} else if language == "fr" {[ + Participants + ]} else {[ + Participants + ]} + ]]], + [ + #for (i, participants) in participants-list.enumerate() [ + #badge(participants.trim(), index: participants-list.len() - i - 1) + ] + ] + ) + ] + + // separator + #align(center)[#line(length: 90%, stroke: 1pt + rams-light-grey)] +] + +// Table of contents +#if show-toc [ + #set text(fill: rams-light-grey, size: 16pt, weight: "bold") + [Contents] + #v(0.5em) + #line(length: 100%, stroke: 1pt + rams-green) + #v(0.5em) + #set text(weight: "medium", fill: rams-white, size: 10pt) + #show outline.entry: it => { + set text(fill: rams-light-grey) + it + } + #outline(indent: auto) + #pagebreak() +] + +#cmarker.render( + read(filepath), + scope: (image: (path, alt: none) => image(path, alt: alt)), + math: mitex +) diff --git a/md-pdf.ron b/md-pdf.ron new file mode 100644 index 0000000..599c7b2 --- /dev/null +++ b/md-pdf.ron @@ -0,0 +1,7 @@ +( + templates_dir: Some("./"), + default_template: Some("darko"), + default_language: Some("en"), + default_toc: Some(false), + default_author: Some("Rémi Heredero"), +)