30 Commits

Author SHA1 Message Date
k3nny 7f404f3492 docs(docs): update README, CHANGELOG, ROADMAP, Taskfile for v0.3.0
ci / vet, staticcheck, test, build (push) Failing after 2m10s
release / Build and publish release (push) Successful in 1m12s
Document glint render, --no-warn, exit codes 2/10, --no-skipped, and
colorized output. Fix Taskfile validate entries: fixtures that now exit
10 (warnings only) require ignore_error: true to pass the validate task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 22:35:55 +02:00
k3nny f79c64cd44 feat(security): security hardening, proxy support, and GL045 HTTP include warning
ci / vet, staticcheck, test, build (push) Failing after 2m32s
release / Build and publish release (push) Successful in 1m17s
Security fixes:
- Path traversal guard in include: local: — paths with ../ that escape
  the repo root are rejected instead of reading arbitrary host files
- HTTP timeout (30 s) on all fetcher requests to prevent indefinite hangs
- Response size cap (10 MiB) via io.LimitReader to prevent memory exhaustion
- Cache directory and file permissions tightened to 0700/0600
- LSP Content-Length cap (64 MiB) to guard against DoS from a malicious client

New feature:
- --proxy flag on check, graph, and lsp subcommands; also proxy: key in
  .glint.yml; overrides system HTTP_PROXY / HTTPS_PROXY env vars when set;
  cmdGraph and cmdLSP now also load .glint.yml for proxy/token/url fallbacks

New lint rule:
- GL045 (Warning): include: remote: using plain http:// instead of https://

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 21:52:57 +02:00
k3nny 4972adb213 feat(cli): add VS Code extension wrapping glint lsp
ci / vet, staticcheck, test, build (push) Failing after 2m27s
release / Build and publish release (push) Successful in 1m19s
editors/vscode/ is a TypeScript VS Code extension that starts glint lsp
as a child process and connects to it via vscode-languageclient. The
documentSelector restricts LSP processing to **/.gitlab-ci.yml so other
YAML files are unaffected. The glint.executablePath setting controls the
binary location (default: glint on PATH). Build with task ext-compile;
package as .vsix with task ext-package. Taskfile tasks added:
ext-install, ext-compile, ext-package. Root .gitignore updated to
exclude node_modules/, out/, and *.vsix from the extension directory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 21:26:23 +02:00
k3nny 2c45b343c2 feat(lsp): add Language Server Protocol server (glint lsp)
ci / vet, staticcheck, test, build (push) Failing after 2m8s
release / Build and publish release (push) Successful in 1m7s
New internal/lsp package implements a minimal JSON-RPC 2.0 LSP server
over stdin/stdout with Content-Length framing. Supported lifecycle:
initialize → initialized → shutdown → exit. Document sync: Full (sends
complete text on every change). Handles textDocument/didOpen,
didChange, didSave, didClose; publishes textDocument/publishDiagnostics
after every change. Rule IDs surface as the diagnostic `code` field
with `"glint"` as source. Parse errors produce an Error diagnostic at
the top of the document. Include resolution is best-effort (GITLAB_TOKEN
env var; ~/.cache/glint default cache). CLI: glint lsp [--token]
[--gitlab-url] [--cache-dir] [--offline].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 01:01:41 +02:00
k3nny 192ab3198b feat(build): GitLab CI component, GitHub Actions action, and pre-commit hook
ci / vet, staticcheck, test, build (push) Failing after 1m52s
release / Build and publish release (push) Successful in 1m13s
templates/check.yml — GitLab CI/CD Catalog component; downloads the glint
Linux binary and runs glint check; inputs: stage, pipeline_file, version,
allow_failure, extra_args.

action.yml — GitHub Actions composite action; downloads glint into
$RUNNER_TEMP and runs glint check; inputs: version, file, args. Mirror
to github.com/k3nny/glint to use as `uses: k3nny/glint@vX.Y.Z`.

.pre-commit-hooks.yaml — language: golang hook; pre-commit builds glint
from source on first run and re-runs on staged .gitlab-ci.yml changes.
Reference as repo: https://git.k3nny.fr/k3nny/glint.

README updated with an Integrations section covering all three.
Git remote corrected to https://git.k3nny.fr/k3nny/glint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 00:12:55 +02:00
k3nny 9342ce0eff feat(build): fuzz testing and git-cliff changelog automation
ci / vet, staticcheck, test, build (push) Failing after 2m24s
release / Build and publish release (push) Successful in 1m19s
Add FuzzParseBytes and FuzzSanitizeYAMLEscapes in internal/model/fuzz_test.go.
Both targets run as regular seed-based tests in CI (go test ./...) and can be
run continuously via `task fuzz` (default 30 s per target; FUZZ_TIME=60s to
extend). Fuzz corpus failures are saved to testdata/fuzz/ for regression.

Add cliff.toml configuring git-cliff to generate Keep-a-Changelog-compatible
release notes from Conventional Commits. New tasks: `task changelog`
(regenerate full CHANGELOG.md) and `task changelog-next` (preview unreleased
entries without writing). Requires git-cliff (brew/cargo install git-cliff).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 23:40:16 +02:00
k3nny 7a4d55ec9a feat(graph): intra-stage DAG sub-columns and connectors behind chips
ci / vet, staticcheck, test, build (push) Failing after 2m58s
release / Build and publish release (push) Successful in 1m25s
When jobs within the same declared GitLab stage have needs: relationships
between each other, the pipeline graph now splits that stage into
topological sub-columns: jobs with no same-stage deps are in sub-column 0,
jobs that depend on them shift one column right. A new computeColumns()
function handles the topo-sort; a narrower subStageGap (20 px vs 50 px
stageGap) separates sub-columns; stage headers span all sub-columns.

SVG connector lines (Bézier curves in DAG mode, bus-bar stubs in classic
mode) are now emitted before job chip rectangles so connectors visually
pass behind chips rather than on top of them.

100% statement coverage maintained (99 tests in graph package).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 23:06:49 +02:00
k3nny 0e51844c3a feat(graph): pipeline graph improvements — on_failure, tooltips, bus-bar connectors, skipped colouring, HTML and Mermaid output
ci / vet, staticcheck, test, build (push) Failing after 1m58s
release / Build and publish release (push) Successful in 1m10s
- when: on_failure visual distinction: red circle (#d9534f), X-mark icon, dashed
  chip border, and Mermaid classDef; legend entry added
- Job tooltip / detail panel: each chip wrapped in <g data-job="…"><title>…</title>
  <desc>…</desc> so SVG viewers and the HTML sidebar show stage, when, image, needs
- Multi-job connector accuracy: classic mode now uses a bus-bar pattern (vertical
  rail at midpoint + per-job stubs) instead of one center-to-center line per stage pair
- Blocked/skipped state colouring: RenderPipeline and pipelineSVG accept
  *cicontext.Context; skipped jobs rendered in grey (#868686) with dimmed text
- Interactive HTML output (--format html): self-contained .html with inline SVG,
  mouse-wheel zoom, drag-to-pan, double-click reset, and a click-to-open sidebar
- Mermaid pipeline output (--format mermaid): prints existing Pipeline() flowchart
  to stdout; suitable for mermaid.live or Markdown embedding
- imageString() helper to extract image name from string or map form
- 100% statement coverage maintained; 809 tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 00:56:06 +02:00
k3nny 8dc30d9207 feat(linter): rules:needs: validation (GL044)
ci / vet, staticcheck, test, build (push) Failing after 2m4s
release / Build and publish release (push) Successful in 1m12s
Add GL044 to validate jobs listed in rules:needs: overrides (GitLab CI
16.4+). rules:needs: lets a specific rule override the job's top-level
needs: list; any referenced job must exist in the pipeline. Unknown jobs
produce an error; optional: true entries produce a warning, matching the
GL027 behaviour for top-level needs:. Cross-pipeline needs and skipped
jobs (when a context is active) are excluded from checking.

Implementation:
- model.Rule gains a Needs []any field (yaml:"needs")
- checkRulesNeeds(p, skipped) added to needs.go; wired into Lint
- GL044 / RuleRulesNeedsUnknown added to rules.go and explain.go
- 6 unit tests + 2 testdata fixtures; task validate updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 00:33:52 +02:00
k3nny 416659ffd8 feat(linter): context-scoped needs:/dependencies: cross-checks
ci / vet, staticcheck, test, build (push) Failing after 1m54s
release / Build and publish release (push) Successful in 1m6s
When glint check runs in single-context mode (--branch, --tag, --source,
or --var), jobs that resolve to JobSkipped against that context are now
excluded from needs: and dependencies: cross-job checks (GL027-GL031).
This eliminates false-positive errors for jobs intentionally gated to
specific pipeline events (e.g. a deploy job with rules:if: CI_COMMIT_TAG
no longer triggers GL027 on branch pipelines).

linter.Lint now accepts a skipped map[string]bool (nil = check all jobs);
checkNeeds and checkDependencies skip jobs present in the map. Multi-context
mode (--context) passes nil, so all jobs are checked regardless of context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 00:25:09 +02:00
k3nny 3b0bcb72d3 feat(cli): multi-context simulation via --context flag
ci / vet, staticcheck, test, build (push) Failing after 2m39s
release / Build and publish release (push) Successful in 1m22s
Add --context KEY=VALUE[,...] (repeatable) to glint check. When two or
more --context flags are given, glint evaluates every pipeline job across
all contexts and prints a side-by-side comparison table:

  glint check --context branch=main --context branch=develop .yml

Each column shows active / manual / skipped / blocked per job. Known
context keys are branch, tag, source (case-insensitive); any other
KEY=VALUE pair is treated as a CI variable override. The --changes /
--changes-from changed-file list is shared across all contexts. Implicit
branch=main / source=push defaults are skipped when --context is used.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 00:12:38 +02:00
k3nny 04f17f8616 test(coverage): add unit tests across all packages; remove dead code
ci / vet, staticcheck, test, build (push) Successful in 2m25s
- Added comprehensive table-driven test suites for all packages:
  cmd/glint, cicontext, fetcher, graph, linter, model, resolver.
  Coverage reaches 98%+ statement coverage across the codebase.
- Replaced os.Exit calls in cmd/glint with an `exit` variable so tests
  can capture exit codes without terminating the test process.
- Removed unreachable code found during coverage analysis:
  dead guard in cicontext.parseRegexLiteral; dead len(jobs)==0 branch
  in graph.Pipeline; skipWin struct field and dead continue in
  graph.convertToPNG; pipelineSVG return type simplified to string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 22:03:46 +02:00
k3nny e13455a629 docs(docs): extract usage examples to USAGE.md, trim README
Move all command-by-command reference (output formats, context
simulation, remote includes, cache/offline, component format, graph
modes, explain, project config, inline suppression, example output)
from README.md into a new USAGE.md. Replace the 326-line Usage section
in README with the commands block and a single link to USAGE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 11:13:02 +02:00
k3nny b8e640de03 docs(docs): extract features to FEATURES.md, trim README
The README features block grew to 38 bullets plus a full lint-rules
table, making the document hard to scan. This commit:

- Creates FEATURES.md with a structured reference covering lint rules
  (GL001–GL043 tables), include resolution, context simulation, output
  formats, configuration, graph visualization, and developer tools.
- Replaces the flat bullet list in README with a 6-line "What it does"
  category summary that links to FEATURES.md and ROADMAP.md.
- Removes the redundant ## Lint rules section from README (now in
  FEATURES.md).
- Adds 'explain' to the commands block in the README Usage section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 11:07:51 +02:00
k3nny 4ce7f86d4d feat(linter): glint explain, GL042 rules:if: reachability, GL043 inherit completeness
- `glint explain <RULE>`: new subcommand printing rule description,
  rationale, bad-YAML example and fix for every GL001–GL043 rule.
  `glint explain` (no arg) lists all rules with ID, severity, title.
  Rule IDs are case-insensitive.

- GL042 (rules:if: evaluated reachability): warns when every rules:if:
  condition evaluates to false given the values of variables declared in
  the pipeline YAML, making the job statically unreachable. Conservative:
  only fires when all referenced variables are declared in YAML; predefined
  CI_* / GITLAB_* variables are skipped to avoid false positives.

- GL043 (inherit: completeness): warns when inherit: default: is declared
  but there is no default: block in the pipeline (dead declaration), or
  when the list form names fields not set in the default: block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 11:02:23 +02:00
k3nny 02d8e63a98 feat(cli): .glint.yml config and inline suppression comments
ci / vet, staticcheck, test, build (push) Successful in 2m25s
release / Build and publish release (push) Successful in 1m24s
Adds project-level configuration and per-job suppression directives:

.glint.yml (searched from pipeline dir up to the git root):
- ignore: [GL007, GL032] — suppress rules globally for the project
- severity: {GL004: warning} — override rule severity (error/warning/ignore)
- stages: [quality] — extra stages beyond the pipeline's stages: block
- token: / url: / cache_dir: — defaults for flags; lower priority than
  CLI flags and environment variables

Inline suppression (# glint: ignore):
- Place "# glint: ignore GL007" immediately before a job definition to
  suppress that rule for the specific job only
- Multiple rules: "# glint: ignore GL007, GL032" (comma or space separated)
- Wildcard: "# glint: ignore all" suppresses every finding for the job
- Suppressions are scoped to the annotated job; pipeline-level findings
  are unaffected
- Parsed from yaml.Node head/line comments in the first parse pass;
  stored in Pipeline.Suppressions (root file only, not includes)

New packages: internal/config (Load, walk-up search, .git boundary stop)
New files: cmd/glint/filter.go (applyConfig, isSuppressed helpers)
Tests: config_test.go, parser_suppress_test.go, filter_test.go
Validate fixtures: testdata/config_ignored/, config_severity/, config_suppress/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 10:23:33 +02:00
k3nny f5f8546bcf feat(cli): output formats, GL034-GL041 lint rules, include inputs and cache
ci / vet, staticcheck, test, build (push) Successful in 2m16s
release / Build and publish release (push) Successful in 1m9s
Bundles three patch releases (v0.2.16–v0.2.18):

v0.2.18 — output formats (--format flag on glint check):
- json: stable JSON report (schema_version: 1, findings array, summary)
- sarif: SARIF 2.1.0 for GitHub Code Scanning / GitLab SAST
- junit: JUnit XML for CI test-report artifacts (artifacts:reports:junit)
- github: GitHub Actions ::error:: / ::warning:: annotation lines
- Unknown --format value exits 2 with a helpful error message
- Summary line routed to stderr in structured formats; context suppressed

v0.2.17 — include resolution improvements:
- Recursive include depth capped at 100 (matches GitLab's own limit)
- project: and component: includes tracked in visited set (cycle detection)
- $[[ inputs.KEY ]] / $[[ inputs.KEY | default(…) ]] substituted from with:
- --cache-dir: persist fetched remote templates to disk (SHA-256 keyed)
- --offline: serve from cache only; defaults to ~/.cache/glint

v0.2.16 — new lint rules (GL034–GL041):
- GL034: services map form requires name; alias must be valid DNS label
- GL035: rules:changes / rules:exists absolute path detection
- GL036: timeout format validation (job-level + default.timeout)
- GL037: id_tokens entries must have an aud key
- GL038: secrets entries must declare a provider (vault / gcp / azure)
- GL039: pages: keyword + artifacts.paths consistency
- GL040: duplicate stage names in stages: list
- GL041: cache.key.files must be exact paths, not globs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 10:09:16 +02:00
k3nny 54b5850835 feat(linter): GL033 static dead-rules detection
ci / vet, staticcheck, test, build (push) Successful in 2m12s
release / Build and publish release (push) Successful in 1m7s
Add rule GL033 that warns when every rule in a job's rules: block has
an explicit when: never, making the job permanently excluded from any
pipeline run. This is a pure static check — no if: evaluation or context
required. Only rules with literal when: never trigger it; rules with no
when: (defaults to on_success), when: manual, when: always, or
when: on_failure are treated as reachable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 09:13:19 +02:00
k3nny 5fee51ec7d fix(cli): consistent output format, sorted findings, version flag
ci / vet, staticcheck, test, build (push) Successful in 2m9s
release / Build and publish release (push) Successful in 1m13s
- Workflow rules now use strict if: evaluation (parse failure → skip rule,
  not match); fixes premature matching that blocked later rules and injected
  wrong variables into the context
- Single = accepted as alias for == in rules:if: expressions
- File/Line preserved through extends: resolution (lost during YAML
  encode/decode round-trip in the resolver)
- Findings sorted by (File, Line, Rule) so same-file issues group together
- All warnings use ruff-style path: [warning] message format (includes,
  extends chains, workflow non-start)
- Add --version / -v flag; version shown at top of every --help output
- Build injects version via ldflags using git describe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 00:13:51 +02:00
k3nny cbed44b1e9 feat(cli): variable expansion, scalar bool/int support, precedence fix
ci / vet, staticcheck, test, build (push) Successful in 2m15s
release / Build and publish release (push) Successful in 1m13s
- Add $VAR / ${VAR} expansion in effective context (ctx.ExpandVars):
  iterates up to 10 passes to resolve transitive chains; circular
  references are left as-is after the limit.
- Handle non-string YAML scalars (bool, int, float64) in
  ExtractStringVars and varValueString via new ScalarString helper;
  values like BUILD: true no longer render as "(complex)" or get
  silently dropped from the effective context.
- Variable precedence (GitLab spec): pipeline defaults < workflow-rule
  vars < CLI --var flags; implemented correctly in enrichContext;
  expansion applied after all sources are merged.
- Update README, CHANGELOG, ROADMAP for v0.2.13.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 12:32:47 +02:00
k3nny fef1536e1b feat(cli): ruff-style output, implicit context defaults, --list-vars
ci / vet, staticcheck, test, build (push) Successful in 2m16s
release / Build and publish release (push) Successful in 1m6s
- Finding format now follows file:line: RULEID [severity] message,
  matching ruff and other modern linters (GL003 [error] job "x": ...)
- glint check and glint graph default to --branch main --source push
  when no context flag is given; rules:if: is always evaluated
- --list-vars flag on both commands prints sorted KEY=VALUE of all
  collected variables (YAML, workflow-rule union, effective context)
- CHANGELOG [Unreleased] promoted to [0.2.11]; README badge updated;
  ROADMAP marks newly shipped items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 00:36:32 +02:00
k3nny 18c8fc82c9 feat(linter): add GL032 variable reference validation in rules:if:
release / Build and publish release (push) Successful in 1m11s
Warn when a $VAR or ${VAR} reference in a rules:if: expression is not
declared in pipeline variables:, the job's own variables:, or any
workflow:rules:variables: block. Predefined GitLab CI namespaces (CI_*,
GITLAB_*, FF_*, RUNNER_*, TRIGGER_*, CHAT_*) are always exempt.

Each undeclared variable is reported at most once per job. The finding
is a WARNING (not an error) because variables may also be set in GitLab
CI/CD project settings, which are invisible to glint at lint time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 23:13:25 +02:00
k3nny f48bf02152 feat(linter): add structured rule IDs (GL001–GL031)
Every Finding now carries a stable Rule string field with a GL### code.
The ID appears in output between the source location and the message:

  [ERROR] job "deploy" (ci.yml:14) GL003: missing required field 'script'
  [WARNING] (ci.yml) GL001: no stages defined

Rules:
  GL001 no-stages          GL002 workflow-when       GL003 missing-script
  GL004 unknown-stage      GL005 only-rules-conflict GL006 except-rules-conflict
  GL007 deprecated-only    GL008 invalid-when        GL009 delayed-no-start-in
  GL010 start-in-no-delayed GL011 invalid-parallel   GL012 invalid-retry
  GL013 invalid-retry-when GL014 invalid-allow-failure GL015 invalid-interruptible
  GL016 trigger-with-script GL017 invalid-trigger    GL018 invalid-coverage
  GL019 invalid-release    GL020 invalid-environment GL021 invalid-artifacts
  GL022 pages-public       GL023 invalid-cache       GL024 invalid-rules-when
  GL025 invalid-image      GL026 invalid-inherit     GL027 needs-unknown
  GL028 needs-stage-order  GL029 needs-cycle         GL030 unknown-dependency
  GL031 dependency-stage

Changes:
- internal/linter/rules.go: new file with all 31 constants + doc comments
- linter.Finding: add Rule string field; String() inserts it before the
  message colon when non-empty; format unchanged when Rule == ""
- All Finding{} literals in linter.go, keywords.go, needs.go,
  dependencies.go updated with the correct Rule: constant
- README.md lint rules table: new ID column added to all four sections
- CHANGELOG.md: entry in [Unreleased]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 22:56:24 +02:00
k3nny e5f926b55f docs: 📝 add logo
release / Build and publish release (push) Successful in 1m47s
2026-06-11 20:40:39 +02:00
k3nny c4ab64391d fix(model): handle YAML map forms that caused unmarshall errors
Variables with value/description/options sub-keys, default.image in map
form, default.before_script / default.after_script as block scalars, and
rules.changes / rules.exists in {paths, compare_to} map form all caused
"yaml: cannot unmarshal !!map into string" because the struct fields were
typed too narrowly.

Changed types in model.Pipeline, model.DefaultConfig, and model.Rule to
accept any to match GitLab CI spec flexibility (13.7+ variable declarations,
15.3+ rules.changes map form, image map form in default block).

Adds testdata/script_multiline.yml covering all these patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 20:25:53 +02:00
k3nny 88f20165db feat(cli)!: subcommand CLI, graph tree mode, local include resolution
BREAKING CHANGES:
- `glint <file>` removed; use `glint check <file>`
- `--graph <mode>` removed; use `glint graph [mode]`
- `--graph-out` renamed to `--out` on `glint graph`

feat(cli): ruff-style subcommands — `glint check` and `glint graph [mode]`
feat(graph): `glint graph tree` — terminal job tree with context annotations
feat(graph): context flags (--branch/--tag/--source/--var) on `glint graph`
feat(resolver): recursive local include resolution from disk
fix(resolver): extends unknown base emits warning instead of fatal error
fix(model): script/before_script/after_script accept block scalar string form
test(linter): Samba project CI fixtures as integration tests
chore(build): fix .gitignore to not exclude cmd/glint/ directory
docs: update CHANGELOG, README, ROADMAP for v0.2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 00:27:28 +02:00
k3nny 51b3e1f297 fix(project): rename tool to glint 2026-06-10 22:40:42 +02:00
k3nny f98e9c42e7 feat(gitlab-sim): 🚀 branches support 2026-06-07 23:13:52 +02:00
k3nny ff0d9b51f3 feat(gitlab-sim): 🚀 ajout graph 2026-06-07 20:13:03 +02:00
k3nny e2334ec12d feat(gitlab-sim): first commit 2026-06-05 01:29:07 +02:00