Skip to content

GitLab Setup & Configuration ​

We use GitLab Self-Managed (Free Tier). This gives us everything we need: issues, boards, milestones, merge requests, CI/CD, wikis, and container registry. Here's how we structure it.

Group & Project Hierarchy ​

flexso-btp/                          # Top-level group
├── templates/                       # Subgroup: project templates
│   ├── cap-fullstack-template        # CAP + Fiori Elements starter
│   ├── cap-api-template              # CAP headless API starter
│   └── ui5-freestyle-template        # UI5 freestyle starter
├── products/                        # Subgroup: our SaaS products
│   ├── product-name-a                # Each product is a project
│   └── product-name-b
├── extensions/                      # Subgroup: customer extensions
│   ├── customer-x-sf-extension       # SuccessFactors ext
│   ├── customer-y-s4-extension       # S/4HANA ext
│   └── customer-z-salesc-extension   # Sales Cloud ext
├── shared-libs/                     # Subgroup: shared libraries
│   ├── flexso-cds-common             # Shared CDS models
│   ├── flexso-ui-components          # Shared UI5/Fiori components
│   └── flexso-btp-utils              # Auth, logging, etc.
└── docs/                            # Subgroup: team knowledge base
    ├── framework                     # This document, living version
    ├── architecture-decisions         # ADRs
    └── runbooks                      # Operational runbooks

Label System ​

Labels are the lifeblood of our board. We use scoped labels (the :: syntax) so only one label per scope can be active at a time. This prevents label soup.

ScopeLabelsPurpose
stage::opportunity spec build review ship operateTracks where the issue is in our 6-stage lifecycle
type::feature bug enhancement tech-debt spike maintenance epicCategorizes the kind of work — epic is the parent issue /spexso-kickstart creates for a traject: it carries no stage:: label, so no marathon ever picks it
priority::critical high medium lowUrgency level
size::xs s m l xlT-shirt sizing for effort estimation
platform::sf s4 salesc btp isWhich SAP system it relates to
ai::ai-assisted ai-reviewed ai-generatedTracks AI involvement for learning
needs::manual-test conflict-resolution attentionPost-merge flags: manual QA needed, auto-merge failed, or a marathon run left the issue for human review
auto-pickauto-pickEligible for the online runner — Spexso proposes it, a human confirms; removed when the issue is done or handed off
redesignredesignSet by /spexso-redesign, or by /spexso-prototype on an issue that does not have it yet: the issue waits for a new design and no picker takes it. /spexso-brainstorm <id> --design docs/design/issue-<id> loads the design and removes it — see Een scherm herontwerpen
release notesskip-release-note/spexso-finish writes no release-note fragment for this issue (only in a project with releaseNotes in its config) — see Release notes
auto-testauto-test-success, auto-test-fail, auto-test-skippedSet by the test server after its browser test; the next run skips every issue with one. Remove the label to have the issue tested again

stage:: is single-value — enforced in code. GitLab's scoped labels give mutual exclusion in the UI, but across automated CLI transitions it isn't guaranteed (and legacy/non-canonical stage labels like a stray stage::wip can linger). Spexso therefore never sets a stage with a raw npx spexso issue update --label; every transition (brainstorm → spec, start → build, finish → ship) goes through npx spexso stage <id> <stage>, which strips every existing stage::* before adding the target. This guarantees exactly one stage label per issue, so the marathon picker can't re-select a shipped issue that still carries stage::spec.

Blocking links on GitLab CE. Typed blocks issue links are a GitLab Premium/Ultimate feature; Community Edition (like flexso.gitlab.host) only accepts relates_to. Spexso detects the refusal and records the dependency as a Blocked by #n line in the dependent issue's description, plus a relates_to link so the relation stays visible in the UI. The marathon, /spexso-start, auto-pick and spexso status read those lines exactly like native links. Writing Blocked by #n (or - Blocked by: #n — title) in a description by hand works too.

Issue Board Configuration ​

Each project gets three kanban boards, provisioned automatically by npx spexso init (and re-runnable via /spexso-boards-init):

BoardColumnsAudience
Workflowstage::* (Opportunity → Operate)Everyone — daily "where is each issue?" view
Needs attentionneeds::manual-test | needs::conflict-resolution | needs::attentionTech lead — what got dropped by autonomous runs
Backlog groomingpriority::* (critical → low)Product / PM — refinement sessions

The provisioning logic lives in lib/boards.js (sister module to lib/labels.js). It delegates to the tracker adapter's syncBoards (also exposed as npx spexso board sync), which calls GitLab's REST endpoints, is idempotent (skips existing boards/columns, never deletes), and enforces the dependency order: labels are created first, boards second.

See boards design for the full rationale and setup-boards scenario for the developer flow.

Milestones ​

Milestones represent delivery cycles, not sprints. We work in 2-week cycles, but a milestone can span multiple cycles for larger initiatives. Every issue is assigned to a milestone. Milestones have due dates. The milestone view in GitLab shows completion percentage — this is our progress report, no slide decks needed.

Marathons: spexso/ milestones ​

Spexso plans marathons with its own milestones, titled spexso/<name> and named after their goal (spexso/Escalations). They are not delivery cycles and not agile sprints: a marathon is the set of specced issues one autonomous run works through, usually in hours. Create and close them with npx spexso milestone create "<name>" / close "<name>", or from the marathon dashboard.

Spexso only reads milestones with the spexso/ prefix. Your team's own milestones (cycles, releases, sprints) never show up in .spexso/index.json or the dashboard. An issue has one milestone, so moving an issue into a marathon replaces its current milestone — the dashboard asks first.