chore: init

This commit is contained in:
2026-03-19 21:05:44 +01:00
commit 446e219014
4 changed files with 572 additions and 0 deletions

85
.github/workflows/build-release.yml vendored Normal file
View File

@@ -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 }}

99
README.md Normal file
View File

@@ -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
<!--pagebreak-->
# 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:
```
<type>(scope): <description>
[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 <john.doe@example.com>
```
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)

381
darko.typ Normal file
View File

@@ -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
)

7
md-pdf.ron Normal file
View File

@@ -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"),
)