Skip to content

Storage & GitLab Sync ​

Source of Truth: GitLab Issues ​

All specs, status, and discussion live as GitLab issues. Humans read and edit them in the GitLab web UI. Functional consultants write specs there. The spexso CLI façade (dispatching to the platform adapters, glab under the hood on GitLab) is the bridge between Claude Code sessions and GitLab.

Local Cache: .spexso/index.json ​

A tiny auto-generated file (~200-300 tokens) that gives Claude awareness of active issues without reading full specs. It's listed in .gitignore — never committed. Regenerated on every /spexso-sync.

.spexso/index.json schema ​

json
{
  "synced_at": "2026-04-01T14:30:00Z",
  "project": "flexso-btp/extensions/customer-x-sf",
  "milestones": [
    { "name": "Escalations", "title": "spexso/Escalations", "due": null, "state": "open" }
  ],
  "issues": [
    {
      "id": 42,
      "title": "Leave request approval workflow",
      "stage": "build",
      "size": "m",
      "type": "feature",
      "assignee": "@dev-a",
      "milestone": "cycle-12",
      "worktree": "issue-42"       // null if not started
    },
    {
      "id": 43,
      "title": "Fix date picker timezone bug",
      "stage": "spec",
      "size": "xs",
      "type": "bug",
      "assignee": null,
      "milestone": "cycle-12",
      "worktree": null
    }
  ]
}

milestones lists the open marathons (spexso/ milestones) — also the ones without issues yet. Other milestones are left out. It is empty on Azure DevOps and when the milestone lookup fails; the sync still writes the issues.

Constitution: .spexso/constitution.md ​

One-page project principles, seeded and validated by npx spexso constitution (code-enforced cap: ≤40 content lines / ≤3500 chars, placeholders rejected). Committed — it's product knowledge, tuned per project. The planner cites articles; the reviewer flags violations. See Spec Templates.

Attempt Counters: .spexso/attempts.json ​

The code-enforced 3-strike rail (see TDD flow). Written by npx spexso attempt <task-id> <phase>, which the build orchestrator runs before every tester/builder dispatch; the verb exits non-zero once the phase ceiling is hit (RED: 2, GREEN: 3). Gitignored — per-worktree working state, never committed. npx spexso attempt reset <task-id> clears a task's counters after its commit; npx spexso attempt reset --all empties the ledger before /spexso-build's first task, because keys are task numbers only and the members of a bundle share one worktree.

json
{
  "4": { "red": 1, "green": 2 }
}

Marathon runtime artifacts: .spexso/plans/ and .spexso/marathon-log/ ​

Both gitignored, both produced by /spexso-marathon. .spexso/plans/issue-{id}.md holds a plan-wave scout's pre-computed plan (parallel.budget > 1); the issue's runner consumes it via /spexso-plan --pre-plan and the orchestrator deletes it after the merge train processes the issue. Each plan file starts with a frontmatter header (issue, base commit SHA, footprint) so a plan left behind by --preview --deep or an interrupted run can be reused by a later marathon — validity is checked mechanically (frontmatter footprint vs git diff since base, through npx spexso fence). .spexso/marathon-log/{id}.jsonl is the runner's per-issue telemetry line, written through npx spexso marathon-log write <id> so started_at/finished_at/duration_seconds come from the clock (the start time is parked in .spexso/marathon-log/{id}.started by marathon-log start <id>) — one file per issue so concurrent wave siblings never share an append target; the orchestrator folds each into the consolidated .spexso/marathon-log.jsonl (also gitignored) after the train, via npx spexso marathon-log fold <id> (mechanical append + delete). .spexso/marathon-state.json (gitignored) holds the circuit breaker's consecutive-failure counter, written by npx spexso breaker <event> before the verb signals (record-then-signal, like attempts.json); npx spexso breaker reset clears it at marathon start.

.spexso/marathon-runs/<id>.json is one run record per marathon (id like 20261026T140312Z: milestone, issues, startedAt, endedAt, outcome, observe log path). npx spexso marathon-run start and finish write it; observe report --run and the dashboard read it. A run still open when the next one starts is closed as abandoned, ending at the time of its last observe note (where it died; its start when there is none), and a leftover .spexso/stop of the dead run is deleted so the new marathon does not quit at its first stop check. The folder carries its own .gitignore (*), so projects initialised earlier need no edit. Folded marathon-log.jsonl lines carry final_status (the merge train's outcome).

.spexso/dashboard.json — the marathon dashboard's port and pid while it runs (gitignored with the rest of .spexso/).

Transient files: tmp-*, demo artifacts, wiki staging ​

Also gitignored, because skills leave them behind when a step fails: .spexso/tmp-*.md (multiline bodies for --description-file, deleted only on success), .spexso/demo-{id}.pid and .spexso/demo-artifacts/ from /spexso-demo (including storageState.json, which holds live session cookies for remote targets), and .spexso/.wiki-* (wiki page staging). init and update both maintain these entries, so a blanket git add -A cannot commit them.

.spexso/config.json ​

Local, committed configuration. Two provider slots plus runtime settings.

Schema ​

json
{
  "tracker": { "provider": "gitlab", "project": "group/repo" },
  "code":    { "provider": "gitlab", "project": "group/repo" },
  "targetBranch": "main",
  "models": {},
  "falseFailPatterns": ["cds: command not found"],
  "testCommand": "npm test",
  "testTargetedCommand": "npm test -- {files}",
  "budgets": { "perTaskMinutes": 30 },
  "modelBySize": { "xs": "sonnet", "s": "sonnet", "m": "opus", "l": "opus", "xl": "opus" },
  "parallel": { "red": true, "green": true, "budget": 1, "sizeScores": { "xs": 1, "s": 1, "m": 2, "l": 3, "xl": 6 } }
}
FieldMeaning
tracker.providerWhere work items live: gitlab | github | azure | calm (CALM is tracker-only)
tracker.projectPlatform-native project reference (see table below)
code.providerWhere source + PRs live: gitlab | github | azure
code.projectPlatform-native project reference
targetBranchDefault merge target (e.g. main)
modelsPer-agent model overrides
falseFailPatternsKnown environmental false-fails (a tool this machine lacks); gates the GREEN-impossible terminal state (see TDD flow). Default shown; set [] to opt out. Since every worktree gets its own npm ci, Cannot find module and cds build are no longer defaults: they hid real failures. Remove them from an older config.
testCommandFull-suite test invocation used by the finish gate and full-suite checks (default npm test)
trainGateThe merge train's local suite: "full" (default) or "targeted" — only the test files the branch changed plus the static checks, with the MR pipeline as the full gate. Applies to the --train sweep and the post-rebase gate; after a conflict repair or without a pipeline the suite runs in full. See Finish
testTargetedCommandTargeted test invocation for one task's tests, {files} placeholder (default npm test -- {files}); set per framework by /spexso-setup
releaseNotesOpt-in for release-note fragments: { "language": "en" }. Present → /spexso-finish writes release-notes/unreleased/{id}.md per issue; npx spexso release-notes collects them. See Release notes per ticket
budgets.perTaskMinutesPer-task wall-clock ceiling (default 30); npx spexso attempt exits non-zero once exceeded — same escalate rail as the 3-strike counter (see TDD flow)
modelBySizeSize-label → model dial for tester/builder dispatches (defaults shown); see Model Selection
bundleBundles in /spexso-marathon and the factory: with a budget above 0 (default 0 = off), each window is one bundle of ready issues, built in the lead's worktree and branch, reviewed once and merged through one MR, so one pipeline. The window fills in picker order while the sizeScores (default xs:2 s:3 m:4 l:5 xl:10; no label → m) fit the budget; the first issue always enters, so with budget 10 an XL builds alone. Only with parallel.budget 1, never under --autopick. See Factory → Bundles.
parallelWave execution switches: red batch-spawns a wave's testers in one message (default true); green batch-spawns builders too (default true; set false if concurrent builders trip over the worktree's shared build artifacts). See TDD execution waves. budget caps /spexso-marathon's concurrent load by weight: the window fills in picker order with issues whose sizeScores (default xs:1 s:1 m:2 l:3 xl:6; no label → m; calibrate with the suggested-sizeScores block in npx spexso observe report — see Observability) sum to at most the budget (default 1 = sequential — opt-in, recommended 6 ≈ three M issues; an XL fills a default budget alone, so it builds solo; integration always stays serial through the merge train). hotspots are path-suffix patterns for always-conflicting files (default: package.json, package-lock.json, db/schema.cds, mta.yaml, manifest.json) — two issues touching the same hotspot, even at different paths, never share a wave and trip the merge-train fence (npx spexso fence); set [] to opt out.
handoffOptional test hand-off after the merge: `{ "testers": [usernames], "fallback": "random"
blockerResolvedAtWhen a blocker stops blocking its dependents: "merged" (default) — once its code is on the target branch (stage::ship / stage::operate), even while a tester still holds the issue open; "closed" — only once the issue is closed (teams that want QA sign-off before dependents are built). Read by /spexso-marathon, /spexso-start, the auto-pick runner and spexso status.
pricingOptional token prices for npx spexso observe report: { "<model id or substring>": { "input", "output", "cacheRead", "cacheWrite" } } in € per million tokens. Default null = the report shows tokens only — spexso ships no price table, because a built-in one goes stale silently. Exact model id wins, else the longest key the id contains ("opus" prices every opus id); an entry missing any of the four fields counts as unpriced. See Observability.

Project reference format per provider ​

ProviderFormatExample
gitlabgroup/path (may be nested)flexso-btp/tools/spexso
githubowner/repoacme-corp/my-app
azureorg/project[/repo]acme/MyProject/my-app
calmtenant + scenario-id (config-only)(settled in CALM sub-spec)

Auto-detection & backward compatibility ​

If the tracker or code section is absent, Spexso detects the platform from the git remote on first run:

Detection (detectProvider in lib/config.js) matches the remote's hostname by substring, case-insensitive:

Hostname containsDetected provider
gitlab (e.g. gitlab.com, flexso.gitlab.host, gitlab.acme.io)gitlab
github (e.g. github.com, github.acme.com)github
dev.azure.com or visualstudio.comazure

Both slots default to the detected platform. An existing GitLab project therefore gets tracker=gitlab, code=gitlab automatically — no config file changes are needed. resolveConfig() in lib/config.js fills in the missing fields but never overwrites existing config.

Mixing platforms ​

The two slots are independently configurable. Example: SAP CALM as the issue tracker, GitLab for code:

json
{
  "tracker": { "provider": "calm",   "project": "tenant/scenario-id" },
  "code":    { "provider": "gitlab", "project": "acme/my-app" }
}

When tracker and code providers differ, spexso commit-ref <id> produces a labeled reference ([calm: 3-123456]) instead of a clickable native one. See Providers (Multi-Platform) for details.

Provider support tiers

GitLab is first-class (fully implemented, field-proven). GitHub is Supported (implemented, contract + golden-shape tested; live e2e pending). Azure DevOps is Best-effort (implemented; az syntax reconstructed from docs, live-verification markers in the source). SAP CALM is tracker-only and a later goal. See Providers (Multi-Platform) for the full breakdown.

Sync Flow ​

npx spexso issue list (Query active issues) → Parse JSON (Extract fields) → Write index (.spexso/index.json) → Detect worktrees (Map to issues). One extra best-effort milestone lookup (listMilestones) fills milestones with the open spexso/ marathons; if it fails, sync still succeeds without it.

Façade Verbs Used ​

Whenspexso verbPurpose
/spexso-syncnpx spexso issue listRefresh local index (open issues, normalized JSON)
/spexso-syncnpx spexso milestone list (listMilestones)Best-effort: fill milestones in the index with open marathons
/spexso-startnpx spexso issue view <id>Pull full spec content
/spexso-brainstormnpx spexso issue createCreate new spec issue
/spexso-plannpx spexso issue note <id>Post plan as comment (also kept as .spexso/plans/issue-<id>.md for the review's UI contract)
/spexso-reviewnpx spexso issue note <id>Post review summary
/spexso-finishnpx spexso pr create + npx spexso pr mergeCreate + merge MR
Stage transitionsnpx spexso stage <id> <stage>Move between stages (single-value: strips other stage::*)