- TypeScript 97.7%
- Shell 1.3%
- Dockerfile 0.6%
- JavaScript 0.3%
- Python 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
`gh release create` is GitHub-only and does not work against this project's Forgejo host — the hint printed after a release build was unusable, and cutting v0.3.0 needed the API calls worked out by hand. - release-build.sh prints the actual publish sequence (signed tag, create release, upload each asset, verify the published install path) - README says Forgejo-only in the cut-a-release section Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
| .agents/skills | ||
| .beads | ||
| .castra | ||
| .claude | ||
| .codex/skills | ||
| .pi | ||
| .work-issue | ||
| attic | ||
| docs | ||
| hack | ||
| manifests | ||
| openspec | ||
| scripts | ||
| specifications | ||
| src | ||
| test | ||
| vendor/pi-ca-leash | ||
| .containerignore | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .sops.yaml | ||
| AGENTS.md | ||
| biome.json | ||
| bun.lock | ||
| bunli.config.ts | ||
| CLAUDE.md | ||
| Containerfile | ||
| install.sh | ||
| LICENSE | ||
| model-catalog.json | ||
| package.json | ||
| README.md | ||
| skills-lock.json | ||
| tsconfig.json | ||
castra
Minimal forge-agnostic CLI for the castra personas — fetch issues, run a persona once, and push its result as a PR, against any supported forge (Forgejo/Codeberg or GitHub).
A clean-room TypeScript reimplementation of the canonical bash castra
(lage/bin/castra, "the norm"): the same forge-agnostic command surface,
backed by a native TS ForgeBackend layer.
Built on Bunli and runs on Bun.
⚠️ Pre-1.0 (
0.2.0). Commands and flags may still change. The previous spellkave orchestrator lives inattic/and is no longer built.
Install
castra ships as a standalone binary — no bun, node or npm needed at runtime.
curl -fsSL https://git.b4mad.industries/agentic-forges/castra/raw/branch/main/install.sh | sh
Installs the latest release into ~/.local/bin. Override with CASTRA_VERSION
(a release tag), CASTRA_PREFIX (install dir) or CASTRA_REPO. Released
targets: linux-x64, linux-arm64, darwin-arm64.
From a checkout, to build and install the binary for this machine:
bun install
bun run install:local # → ~/.local/bin/castra
Cutting a release
bun run release:build # → dist/release/castra-<target>.gz + SHA256SUMS
Upload every file in dist/release/ to the release tagged v<version>;
install.sh fetches from there and verifies the checksum. This project is
Forgejo-only — publish via the Forgejo API, not gh; the script prints the
exact calls. Cross-compiling
needs the native optional deps for all targets (bun install --os '*' --cpu '*', which the script does for you) — bunli pulls in OpenTUI, which ships a
per-platform binary.
Development
bun dev -- <command> # run from source (bun run src/index.ts)
bun run build # bundle with bunli → ./dist
bun test # run the test suite
bun run typecheck # tsgo --noEmit
Usage
castra issue view <URL> # fetch + project an issue as JSON
castra prepare-run --persona code --issue 42 # isolated git worktree for a run
castra persona-run --persona code --input … --output-dir /tmp/out
castra persona-validate --persona intake --file out/agent-result.json # pre-mutation verdict gate
castra post-run --persona code <issue-url> # push branch + open/update PR
castra status start <issue-url> --persona code # upsert a pipeline-status comment
castra label list --repo owner/repo # dump a repo's label vocabulary
castra label mutex acquire <issue-url> # per-issue in-progress mutex (castra-hp1)
castra selftest # verify forge connectivity
Commands
| Command | What it does |
|---|---|
issue view <URL> |
Fetch an issue and project the requested fields (--json number,title,body,state,labels,comments). |
prepare-run |
Create an isolated git worktree (castra/<persona>/<issue>) under .worktrees/. |
persona-run |
Render a persona prompt, spawn its driver, persist the output per outputMode. |
post-run |
Inspect git state, push the feature branch, open/update its PR, run the review-label handoff. |
persona-validate |
Validate a persona result file against the shared verdict predicate (verdictIssues) — the pre-mutation gate a post-script calls instead of hand-rolled jq checks. |
init-labels |
Apply the declarative label set (labels.json) to a repo. Idempotent; JSONL events. |
label list |
Dump a repo's live label vocabulary as JSON — forge-agnostic replacement for gh label list. |
label mutex acquire|release |
Per-issue castra:bot-active in-progress mutex (castra-hp1) — Tekton pipelines acquire it as their first task and release it in finally, so at most one lane runs per issue at a time. Advisory — never fails the pipeline. |
selftest |
Hit a known issue and assert the normalized castra shape. |
status |
Upsert a per-run pipeline-status comment on the triggering issue/PR (start/finish). Advisory — never fails the pipeline. |
version |
Print castra v<base>+<short-sha>. |
Persisting output to OTEL (--persist-log)
A global opt-in (any command): tee the command's raw stdout and stderr
(interleaved in write order) to the OpenTelemetry collector as an OTLP log
record, so a run's full output is queryable alongside its pipeline traces.
Capturing stderr matters — commands like persona-run write their progress
there. Off by default.
castra --persist-log persona-run --persona code … # global flag
CASTRA_PERSIST_LOG=1 castra version # env equivalent
Enabled by the --persist-log flag or CASTRA_PERSIST_LOG=1 (truthy:
1/true/yes/on); the flag wins, so --persist-log=false overrides a
truthy env. When on, castra fails fast if the OTLP endpoint is not
configured. Config (standard OTLP env vars):
| Env var | Purpose |
|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Base collector URL; /v1/logs is appended. Required when persist-log is on. Backends that namespace by tenant need the org in the path — e.g. OpenObserve wants …:5080/api/default so the POST lands at /api/default/v1/logs. |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT |
Logs-specific URL, used verbatim (overrides the base). |
OTEL_EXPORTER_OTLP_HEADERS |
key=value,key=value export headers (e.g. auth). |
OTEL_SERVICE_NAME |
service.name attribute (default castra). |
CASTRA_PERSIST_LOG_FILE |
PVC mirror path (default out/raw-stdout.txt; off/empty disables). |
The export and file mirror are best-effort (a collector hiccup warns to stderr,
never fails the command); only the missing-endpoint check is fatal. Run/issue
correlation attributes (castra.run_id, castra.issue) come from
CASTRA_RUN_ID/ISSUE_NUMBER when present. On-cluster this is wired through the
castra-agent-env secret — set CASTRA_PERSIST_LOG: "1" there to enable it for
all persona lanes (see manifests/castra/).
Forge backend
Operations are served by a native ForgeBackend (src/forge/), selected from
the environment:
- Auth:
FORGE_ACCESS_TOKEN(required). Instance:FORGE_URL(defaulthttps://codeberg.org). - Driver:
FORGE_DRIVER(forgejo-mcp|github), else auto-detected fromFORGE_URL(github.com→github, otherwiseforgejo-mcp). forgejo-mcpspawns the version-pinnedforgejo-mcp --cli;githubtalks the REST API overfetch.
Failure auto-filing
When persona-run (stdout-json mode) gets agent output that fails the shared
verdict predicate (verdictIssues: not a complete JSON object, or — for
personas with a registered schema, e.g. intake — a shape error), it still
fails non-zero — but, if CASTRA_FAILURE_REPO (owner/repo)
is set, it first opens a forge issue there with the run context and a transcript
snippet so the failure is debuggable. Repeat failures dedup onto the same issue as
comments rather than spawning duplicates. Left unset, the behaviour is skipped
(logged, not fatal). CASTRA_RUN_ID (e.g. the PipelineRun name) is recorded in the
issue when present. The failure repo is deliberately separate from the processed
repo (REPO_FULL_NAME) — a castra/agent bug is not the customer repo's problem.
Deployment
The CLI ships as a container image and is driven by issue-label-triggered Tekton pipelines on OpenShift, delivered via ArgoCD:
manifests/castra/— EventListener, triggers, tasks, and pipelines (code,propose,intake,checker-tobi). A label added to an issue on GitHub or Codeberg/Forgejo starts the matchingPipelineRun. See its README.manifests/telemetry/— in-cluster OpenObserve OTLP backend that receives the agent run steps' OpenTelemetry traces/metrics/logs. See its README.manifests/environment/nostromo/— per-cluster sealed secrets (forge identity, git auth, webhook, telemetry credentials).manifests/argocd/— the ArgoCDApplication.
Documentation
- Product & architecture canon:
specifications/ - Architecture decisions:
specifications/adr/ - Feature/domain canon & workflow:
openspec/ - Contributor & agent guide:
CLAUDE.md(AGENTS.mdpoints here)
Issue tracking uses bd (beads) — run bd prime for workflow context.
License
GPL-3.0-or-later