Package Architecture
Spexso ships as an npm package that, when run with npx spexso init, scaffolds the entire Claude Code integration into the target repository. No global install needed — each project gets its own copy of the commands and agents.
What npx spexso init creates
your-project/
├── .claude/
│ ├── commands/ # Slash commands (22 total)
│ │ ├── spexso-kickstart.md # /spexso-kickstart
│ │ ├── spexso-wizard.md # /spexso-wizard
│ │ ├── spexso-architecture.md # /spexso-architecture
│ │ ├── spexso-brainstorm.md # /spexso-brainstorm
│ │ ├── spexso-start.md # /spexso-start
│ │ ├── spexso-plan.md # /spexso-plan
│ │ ├── spexso-build.md # /spexso-build
│ │ ├── spexso-review.md # /spexso-review
│ │ ├── spexso-finish.md # /spexso-finish
│ │ ├── spexso-sync.md # /spexso-sync
│ │ └── … # + 11 more — see Complete Command Reference
│ ├── agents/ # Custom subagents (6 total)
│ │ ├── spexso-planner.md # Breaks spec into tasks
│ │ ├── spexso-builder.md # TDD implementation agent
│ │ ├── spexso-reviewer.md # Code review with severity
│ │ ├── spexso-tester.md # Test-first generation agent
│ │ ├── spexso-researcher.md # Competitive research (/spexso-tinder)
│ │ └── spexso-issue-runner.md # Per-issue sequencer (/spexso-marathon)
│ └── settings.json # Permissions + hooks (see below)
├── .spexso/
│ ├── index.json # Auto-generated issue cache
│ ├── config.json # Provider config, models, budgets — see Storage
│ ├── rules/ # Domain rulebooks — baseline + local pair
│ │ ├── doctrine.md # Injected every session via SessionStart hook
│ │ ├── typescript.md, cap.md, ui5.md, ux.md, test.md, abap.md # spexso's, refreshed on init
│ │ ├── *.local.md # yours — additive, committed, never touched by spexso
│ │ ├── .baselines.json # which baseline was installed (drives the refresh)
│ └── templates/ # GitLab issue templates + registries
│ ├── spec-full.md # M/L/XL spec template
│ ├── spec-lite.md # XS/S lite spec template
│ ├── bug-report.md # Bug template
│ ├── constitution.md # Seeds .spexso/constitution.md
│ ├── btp-services.md # BTP service-binding registry
│ ├── tenant-requirements.md # Multi-tenant scope registry
│ └── learning-card.md # Seeds docs/learning/ on /spexso-finish
├── AGENTS.md # Canonical instructions (Spexso section between spexso:start/end markers)
├── CLAUDE.md # @AGENTS.md import shim
└── .gitignore # runtime state under .spexso/ (index.json, worktrees/, plans/, marathon-log, tmp-*, demo-artifacts/, …) — added by init and updateAdditionally, npx spexso init:
- Creates all GitLab scoped labels (
stage::*,type::*,size::*,priority::*,platform::*,ai::*,needs::*) vianpx spexso label create— idempotent, skips labels that already exist - Checks if the recommended Claude Code plugin (sap-skills) is installed and shows install commands if missing — advisory only, never blocking
- Tells the user to run
/spexso-setupas the next step
The file half of init lives in lib/install.js and is also exposed alone as npx spexso update: offline, no prompts, for after an npm upgrade. It re-copies the spexso-owned files, refreshes the marked Spexso section of AGENTS.md, re-stamps per-agent models (agents sync), adds any missing spexso entries to .gitignore and writes .spexso/version. The network steps (labels, boards, sync) and project files (config.json, README, CHANGELOG, CLAUDE.md) are init-only. See onboarding.
Key design principle
Spexso keeps one small config file — .spexso/config.json (provider, target branch, models, budgets, parallel switches) — and convention for everything else. It is not user-tuned prose; it's a small, code-read settings file, distinct from the slash commands and agent definitions that carry the actual process.
.claude/settings.json also ships a UserPromptSubmit hook (spexso title) that names the session after the issue a /spexso-<cmd> <id> prompt works on — #1473 Connector Actions run history, the title taken from .spexso/index.json — so parallel terminals can be told apart. It sets the title through the hook output's sessionTitle field; free-form prompts print nothing and keep Claude Code's own automatic title. Claude Code accepts sessionTitle only from UserPromptSubmit and SessionStart, so a yolo chain moving from stage to stage can't update it: the title names the issue, never the stage. (Writing terminal-title escape codes from a hook does not work on Windows, where the hook's console is hidden.)
Beside it are two more hook sets: SessionStart + SubagentStart synchronously inject the doctrine rulebook (spexso doctrine — its stdout IS the injected context) so every session/subagent sees it without CLAUDE.md bloat, and fail-open async observe hooks on SubagentStart / PostToolUse / SubagentStop / Stop each pipe to spexso observe, appending the run's event trace to .spexso/observability/runs/ — see Observability. Hooks invoke the CLI directly — node "${CLAUDE_PROJECT_DIR}/node_modules/@flexso/spexso/bin/spexso.js" … — never via npx: the doctrine hook sits on the critical path of every subagent spawn and the observe hook fires on every tool call, so the npx resolution overhead (hundreds of ms per invocation on Windows) would be paid hundreds of times per issue.
npm Package Structure
spexso/
├── bin/
│ └── spexso.js # CLI entry point
├── src/ # CANONICAL — humans edit only here
│ ├── skills/<name>/SKILL.md # 22 workflow command definitions
│ ├── agents/ # 6 subagent definitions
│ ├── rules/ # Shipped domain rulebooks
│ ├── templates/ # Spec templates
│ ├── snippets/AGENTS-snippet.md # Spexso section for AGENTS.md (canonical; CLAUDE.md = @AGENTS.md shim)
│ └── policy/settings.json # Claude Code permissions
├── dist/ # GENERATED, COMMITTED — never hand-edited
│ ├── claude-code/ # commands/, agents/, settings.json, snippet
│ ├── opencode/ # commands/, agents/ (provider/model-ids), opencode.spexso.json
│ ├── codex/ # skills/ (.agents/skills spec path), agents/*.toml,
│ │ # hooks.json (deny hook), config.spexso.toml, AGENTS-append.md
│ └── shared/ # rules/, templates/ (harness-independent)
├── scripts/build.js # src → dist renderer (exports render())
├── scaffold/
│ └── cicd/ # CI/CD templates, shipped verbatim
├── lib/
│ ├── init.js # Install logic (copies from dist/)
│ ├── sync.js # tracker adapter → index.json sync
│ └── facade.js # `spexso <verb>` façade → platform adapters
└── package.jsondist/ is committed on purpose: spexso init stays a plain file copy with no build-at-install, and the npm package ships ready-made artifacts. test/dist-staleness.test.js regenerates every artifact in memory and byte-compares against the committed file — hand-editing dist/ fails CI within one push.
Harness targets
spexso init --harness <claude-code|opencode|codex|all> selects which harness artifacts install (default: claude-code; opencode/codex are auto-added when the project already has .opencode/opencode.json or .codex). Prose bodies are verbatim across harnesses (superpowers' rule) — only frontmatter and placement differ, and scripts/check-canaries.js asserts the load-bearing sentences survive into every rendered artifact:
| Feature | Claude Code | opencode | Codex |
|---|---|---|---|
| Commands | .claude/commands/*.md, /name | .opencode/commands/*.md (same body + description frontmatter), /name | Agent Skills at .agents/skills/<name>/SKILL.md (spec path), $name; AGENTS.md index as discoverability floor |
| Subagents | .claude/agents/*.md, model tier as-is (opus/sonnet) | .opencode/agents/*.md, mode: subagent + provider model-id from src/policy/models.json | .codex/agents/*.toml, model-id from the same map; depth cap 1 fits orchestrator→worker |
| Permissions | .claude/settings.json allow/deny arrays | permission map deep-merged into opencode.json — deny rules emitted last (last-match-wins); user keys always win | No per-command allowlist — spexso guard, a PreToolUse command hook in .codex/hooks.json, exits 2 on a deny-list match; allow-list is advisory |
| Config | — | merged opencode.json | spexso sections appended to .codex/config.toml (marker-guarded, incl. the spexso-yolo profile for headless marathon) |
| Doctrine rail | SessionStart/SubagentStart hooks inject via stdout | instructions: [".spexso/rules/doctrine.md"] in the merged opencode.json — loaded live every session (subagents don't get a re-injection) | same hooks as Claude Code in .codex/hooks.json (spexso doctrine --hook <event>) |
| Observe rail | hooks on SubagentStart/PostToolUse/SubagentStop/Stop | .opencode/plugins/spexso-observe.js on tool.execute.before — tool events only, no subagent spans/phases | same hooks as Claude Code in .codex/hooks.json |
| Rules/templates | .spexso/ (harness-independent) | same | same |
| Safety backstop | CLI-side refusals (npx spexso verbs) | same | same — harness-independent |
spexso init prints one rails line per non-Claude harness saying which rails are active vs unavailable there — a degraded rail is announced, never silent. Per-rail detail: Observability → Harness support.
Codex caveats (documented degradations): all rails ride the hooks system — the user must trust the generated hooks once via /hooks, and the Phase 3 parity suite's P5 probe (force-push denial) stays the mandatory gate before relying on the deny rail; the model ids in src/policy/models.json are tunable defaults.