docs(ISSUE-WP-0006): Forgejo-only language and projection boundary
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 34s

Prefer Forgejo as the self-hosted forge product. Keep Gitea only as the
Gitea-compatible API identifier (module backends/gitea, type string
gitea). FORGEJO_TOKEN is preferred; GITEA_* remains a deprecated alias.

INTENT/SCOPE quote ACT-ADR-005: issue-core is not the fleet ops claim
queue. Connector docs describe repo work record → hub index → optional
Forgejo projection, not activity-core → issue-core → harness.

Assistant: grok
Assistant-Session: 01a09dc6-3f0d-7c93-8b11-8e83c0623d49
This commit is contained in:
tegwick 2026-09-14 04:52:58 +02:00
parent f9d276dadf
commit ee9b85215d
34 changed files with 410 additions and 263 deletions

View file

@ -1,4 +1,4 @@
**Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Gitea, SQLite, GitHub). Renamed from issue-facade on 2026-05-17.
**Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Forgejo, SQLite, GitHub). Renamed from issue-facade on 2026-05-17.
**Domain:** infotech
**Repo slug:** issue-core

View file

@ -2,7 +2,7 @@
## Repo Identity
**Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Gitea, SQLite, GitHub). Renamed from issue-facade on 2026-05-17.
**Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Forgejo, SQLite, GitHub). Renamed from issue-facade on 2026-05-17.
**Domain:** infotech
**Repo slug:** issue-core

View file

@ -1,24 +1,55 @@
# Agent Integration Guide
**Issue Core for Autonomous Coding Agent Coordination**
**Issue Core for external tracker projection** — not the fleet ops queue.
## Purpose
The **Issue Core** capability provides a standardized interface for autonomous coding agents to operate on **external issue trackers** (GitHub, GitLab, Gitea/Forgejo). Instead of agents learning platform-specific APIs, they use a unified abstraction.
The **Issue Core** capability provides a standardized interface for autonomous
coding agents to operate on **external issue trackers** (Forgejo, GitHub,
GitLab, Jira). Instead of agents learning platform-specific APIs, they use a
unified abstraction. The self-hosted forge is **Forgejo** only; the HTTP API
is Gitea-compatible (`issue_core.backends.gitea`, backend type `gitea`).
**Fleet note (2026-07-20):** Internal work originates as repo work records (ADR-001 / work-record canon), not as issue-core issues. Use this guide when an agent must project to or work inside a third-party tracker. See `INTENT.md` and `docs/uuid-external-id-mapping.md`.
**Fleet note (2026-07-20 / ISSUE-WP-0006):** Internal work originates as repo
work records (ADR-001 / work-record canon), not as issue-core issues. Use this
guide when an agent must project to or work inside a third-party tracker. See
`INTENT.md` and `docs/uuid-external-id-mapping.md`.
### Happy path (projection, not automation loop)
```
1. Author work record in repo (ADR-001)
│ fix-consistency
v
state-hub index (UUIDv7 write-back)
│ optional project / link
v
2. issue-core ──► Forgejo (or GitHub / Jira later)
3. mapping: work-record UUID ↔ (backend=forgejo, external_id)
stored backend type string is currently `gitea`
(Forgejo Gitea-compatible API)
```
Internal scheduled automation (FI/Binky, mail, …) does **not** follow
`activity-core → issue-core → harness`. That path is **legacy / external
tickets only**. Claim/execute for fleet ops is activity-core **`ops_run`**
(ACT-ADR-005). See rein-aharness `docs/task-intake.md` and ISSUE-WP-0006.
### When to use a tracker (and when not to)
**Fleet claim/execute** runs on work records (repo files + state-hub), not on
Forgejo by default. Use issue-core when:
**Fleet claim/execute** runs on work records (repo files + state-hub) or on
`ops_run` for scheduled automation — not on Forgejo by default. Use
issue-core when:
- A counterparty or OSS workflow lives in Gitea/GitHub/Jira
- A counterparty or OSS workflow lives in Forgejo/GitHub/Jira
- You need to **project or link** a work record (`issue project` / `issue map`)
- You must update or comment on an **external** issue already in a tracker
Do **not** treat issue-core as the org task board or as origin of intake/tasks.
Findings → `kind: intake` + promotion; optional later projection.
Do **not** treat issue-core as the org task board, the ops claim queue, or
the origin of intake/tasks. Findings → `kind: intake` + promotion; optional
later projection.
## Current Status: Production-Ready with Manual Setup
@ -31,7 +62,7 @@ Findings → `kind: intake` + promotion; optional later projection.
- Milestone operations
- Comment threads
**Gitea Backend** (Production-Ready)
**Forgejo Backend** (Production-Ready; Gitea-compatible API)
- Complete API integration
- Rate limiting and error handling
- State mapping (open/in_progress/blocked → open/closed)
@ -76,18 +107,19 @@ pip install -e .
### 2. Backend Configuration (One-Time Setup)
**For Gitea Projects:**
**For Forgejo Projects:**
```bash
# Configure Gitea backend
export GITEA_API_TOKEN="your-token-here"
# Configure Forgejo backend (type string `gitea` = Gitea-compatible API)
export FORGEJO_TOKEN="your-token-here"
# Deprecated aliases: FORGEJO_API_TOKEN, GITEA_API_TOKEN, GITEA_TOKEN
issue backend add my-project gitea
# Prompts for:
# - Gitea URL: https://gitea.example.com
# - Forgejo URL: https://forgejo.example.com
# - Owner: your-org
# - Repo: your-project
# - Token: (reads from GITEA_API_TOKEN)
# - Token: (reads FORGEJO_TOKEN, then deprecated GITEA_* aliases)
# Verify connection
issue backend test my-project
@ -187,8 +219,8 @@ from datetime import datetime, timezone
backend = GiteaBackend()
backend.connect({
'base_url': 'https://gitea.example.com',
'token': os.environ['GITEA_API_TOKEN'],
'base_url': os.environ.get('FORGEJO_URL', 'https://forgejo.example.com'),
'token': os.environ.get('FORGEJO_TOKEN') or os.environ['GITEA_API_TOKEN'],
'owner': 'myorg',
'repo': 'myproject'
})
@ -236,10 +268,10 @@ import os
# Initialize backend
backend = GiteaBackend()
backend.connect({
'base_url': os.environ['GITEA_URL'],
'token': os.environ['GITEA_API_TOKEN'],
'owner': os.environ['GITEA_OWNER'],
'repo': os.environ['GITEA_REPO']
'base_url': os.environ.get('FORGEJO_URL') or os.environ['GITEA_URL'],
'token': os.environ.get('FORGEJO_TOKEN') or os.environ.get('FORGEJO_API_TOKEN') or os.environ['GITEA_API_TOKEN'],
'owner': os.environ.get('FORGEJO_OWNER') or os.environ['GITEA_OWNER'],
'repo': os.environ.get('FORGEJO_REPO') or os.environ['GITEA_REPO']
})
# Query issues
@ -421,14 +453,14 @@ results = read_agent_messages(42, 'implementation_complete')
```bash
# Pull all issues to local backup
issue backend add backup local
issue sync pull gitea-remote backup
issue sync pull forgejo-remote backup
# Work offline with local backend
issue backend set-default backup
issue create "Offline work item" --label=offline
# Sync back when online
issue sync push backup gitea-remote
issue sync push backup forgejo-remote
```
### Conflict Handling
@ -509,20 +541,20 @@ Create a setup script for each project:
# setup-issue-tracking.sh
cat > .issue-core-config << EOF
GITEA_URL=https://gitea.example.com
GITEA_OWNER=myorg
GITEA_REPO=myproject
GITEA_TOKEN_FILE=~/.secrets/gitea-token
FORGEJO_URL=https://forgejo.example.com
FORGEJO_OWNER=myorg
FORGEJO_REPO=myproject
FORGEJO_TOKEN_FILE=~/.secrets/forgejo-token
EOF
# Load config and configure backend
source .issue-core-config
export GITEA_API_TOKEN=$(cat $GITEA_TOKEN_FILE)
export FORGEJO_TOKEN=$(cat $FORGEJO_TOKEN_FILE)
issue backend add $(basename $(pwd)) gitea <<INPUT
$GITEA_URL
$GITEA_OWNER
$GITEA_REPO
$FORGEJO_URL
$FORGEJO_OWNER
$FORGEJO_REPO
INPUT
issue backend set-default $(basename $(pwd))
@ -642,7 +674,7 @@ issue backend add myproject gitea
issue backend test myproject
# Reconfigure with correct token
export GITEA_API_TOKEN="new-token"
export FORGEJO_TOKEN="new-token"
issue backend remove myproject
issue backend add myproject gitea
```
@ -650,7 +682,7 @@ issue backend add myproject gitea
### "Issue not found"
```python
# Gitea uses backend_id, not number
# Forgejo uses backend_id, not number
issue = backend.get_issue_by_number(42) # Correct
# issue = backend.get_issue("42") # Wrong - needs backend_id
```

View file

@ -6,14 +6,14 @@ metadata:
version: 0.2.1
type: connector
description: >
Backend-agnostic connector to external issue trackers (Gitea/Forgejo, SQLite).
Backend-agnostic connector to external issue trackers (Forgejo, SQLite).
Maps work-record UUIDs to external issue ids. Not the origin of fleet work records.
# What problems this capability solves
purpose:
primary: External tracker projection and ops (work-record-aware connector)
problems_solved:
- Direct API calls to Gitea/GitHub/GitLab (credential sprawl)
- Direct API calls to Forgejo/GitHub/GitLab (credential sprawl)
- No durable link from work-record UUID to tracker issue
- Platform-specific agent code for tracker CRUD
- Offline SQLite cache / backend sync for tracker data
@ -21,7 +21,7 @@ purpose:
# When agents should use this capability
usage_rules:
MUST_USE_INSTEAD_OF:
- "Direct Gitea API calls (requests.post to /api/v1/repos/...)"
- "Direct Forgejo API calls (requests.post to /api/v1/repos/...)"
- "GitHub CLI (gh issue create/list/...)"
- "GitLab CLI (glab issue create/list/...)"
- "Python libraries (PyGithub, python-gitlab) for routine tracker ops"
@ -67,7 +67,7 @@ integration:
required: true
method: manual # v1.0 - auto in v1.1
steps:
- "Export GITEA_API_TOKEN environment variable"
- "Export FORGEJO_TOKEN environment variable (GITEA_API_TOKEN is a deprecated alias)"
- "Run: issue backend add myproject gitea"
- "Provide: URL, owner, repo when prompted"
- "Run: issue backend set-default myproject"
@ -115,8 +115,9 @@ efficiency:
credentials:
method: environment_variables
variables:
- GITEA_API_TOKEN
- GITEA_URL (optional with config)
- FORGEJO_TOKEN (preferred; FORGEJO_API_TOKEN also accepted)
- GITEA_API_TOKEN / GITEA_TOKEN (deprecated aliases)
- FORGEJO_URL (optional with config; GITEA_URL deprecated alias)
security:
- "Tokens never in code or logs"
@ -259,7 +260,7 @@ support:
solution: "Run: issue backend add <name> <type>"
- problem: "Authentication failed"
solution: "Check GITEA_API_TOKEN is set and valid"
solution: "Check FORGEJO_TOKEN is set and valid (legacy GITEA_API_TOKEN still accepted)"
- problem: "Command not found: issue"
solution: "Run: pip install -e capabilities/issue-core/"

View file

@ -9,11 +9,12 @@ state-hub, under a closed kind registry (`workplan`, `task`, `intake`,
`the-custodian/canon/standards/work-record-types_v0.1.md` and the founder-
reviewed architecture draft (`WorkOrchestrationArchitectureDraft.md` v0.2).
External issue trackers (Forgejo / Gitea, GitHub, Jira, …) remain necessary
External issue trackers (Forgejo, GitHub, Jira, …) remain necessary
when a human counterparty, open-source workflow, or third-party process lives
there. They are **not** the fleet coordination substrate and are **not** a
work-record kind. Canon is explicit: *issue-core issues become external
projections only.*
projections only.* The self-hosted forge product is **Forgejo** only
(ACT-ADR-005); Gitea is not a supported second product or migration target.
issue-core exists so that, **when an external tracker is actually in use**,
the fleet has one backend-agnostic surface to project, query, and update
@ -32,9 +33,11 @@ calls with no stable back-reference to the work record.
issue-core was originally built and documented as a **task landing zone**: a
single place where humans, activity-core, and agents filed work via CLI /
REST, with Gitea as the default store. That framing contradicted file-first
work records and produced a live incident (`daily-todo-md-stale-review`
IssueSink → Forgejo issues that were never fleet work).
REST, with the self-hosted forge as the default store (product language then
said Gitea; the fleet forge is **Forgejo**). That framing contradicted
file-first work records and produced a live incident
(`daily-todo-md-stale-review` → IssueSink → Forgejo issues that were never
fleet work).
Architecture draft §4.2 and the work-record types standard (CUST-WP-0060)
retargeted purpose: **connector, not origin**. Capability (CRUD, backends,
@ -51,13 +54,16 @@ execution loop.
- **Tracker CRUD** on configured backends: create, read, update, close /
reopen, comment (and related label / assignee / milestone operations).
- **Backends:** local SQLite (offline store / cache), Gitea (Forgejo-
compatible in deployment). Further backends (GitHub, GitLab, Jira) are
product growth, not yet implemented.
- **Backends:** local SQLite (offline store / cache), **Forgejo** (Gitea-
compatible API; Python module `issue_core.backends.gitea`, backend type
string `gitea`). Further backends (GitHub, GitLab, Jira) are product
growth, not yet implemented. Gitea is not a second supported product.
- **CLI** (`issue` / `issue-core`) and **Python library** for direct backend use.
- **REST** (`issue serve`, optional `[api]` extra): intentional create
(`POST /issues/`), list/get/claim (`GET`/`PATCH /issues/`).
- **Backend↔backend sync** via CLI (e.g. Gitea ↔ SQLite) — *not* the same as
- **REST** (`issue serve`, optional `[api]` extra): intentional **external
tracker** create (`POST /issues/`), list/get (`GET`/`PATCH /issues/`).
REST claim/list is for tracker issues already on a backend — **not** the
fleet ops claim queue (see below).
- **Backend↔backend sync** via CLI (e.g. Forgejo ↔ SQLite) — *not* the same as
work-record boundary sync below.
- **Optional intentional ingestion** of TaskSpec payloads for clients that
deliberately create tracker issues (not the fleet path for internal findings).
@ -104,10 +110,23 @@ connector must add **no coordination load** to the internal loop.
Emitter policy: activity-core **ACTIVITY-WP-0022** (from ISSUE-WP-0004-T05 /
CUST-WP-0060).
- **Not the fleet ops claim queue.** Internal scheduled automation
(FI daily brief, Binky rhythm, mail intake, …) **claim and execute** via
activity-core **`ops_run`** ([ACT-ADR-005](../activity-core/docs/adr/adr-005-ops-runs-vs-dev-work-records.md)).
issue-core does **not** provide that queue. `POST /issues/` creates or
links an **external tracker** issue only. The default activity-core sink
remains state-hub progress + `ops_run`**not** REST to issue-core for
Binky/FI. Do not poll issue-core as the primary automation loop.
ACT-ADR-005: *issue-cores correct role is a connector facade over
external trackers (Forgejo, GitHub, Jira, …). It is not the origin of
work records and not the default internal ops queue. Gitea is out of
scope for this fleet; the self-hosted forge is Forgejo.*
- **Not a second autonomy or budget model.** `lane`, `tags`, and budgets live
on the work record. Projections carry only what external collab needs
(title, body, agreed labels); they must not invent parallel lane/budget
semantics on Gitea labels without explicit rules.
semantics on Forgejo labels without explicit rules.
- **Not a project manager, spawn audit trail, event bus, notification
system, or workflow engine.** Plans and dependencies are workplan tooling;
@ -151,8 +170,8 @@ cancel`) and tracker **`IssueState`** (`open` / `in_progress` / `blocked` /
+---+------+------+--+
| | |
v v v
Gitea/ SQLite GitHub
Forgejo cache (planned)
Forgejo SQLite GitHub
cache (planned)
```
**Primary fleet path (coordination):**
@ -205,3 +224,7 @@ cancel`) and tracker **`IssueState`** (`open` / `in_progress` / `blocked` /
connector decision.
- `the-custodian/canon/architecture/adr-001-workplans-as-repo-artefacts.md`.
- activity-core **ACTIVITY-WP-0022** — IssueSink default policy (emitter side).
- activity-core **ACT-ADR-005** — ops runs vs work records; issue-core is not
the ops claim queue.
- `workplans/ISSUE-WP-0006-forgejo-only-projection-boundary.md` — Forgejo-only
product language and projection boundary.

View file

@ -22,7 +22,7 @@ help: ## Show issue core capability help
@echo " issue-backend-list List configured backends"
@echo " issue-backend-detect Auto-detect backend from repository"
@echo " issue-backend-set-local Configure local SQLite backend"
@echo " issue-backend-set-gitea Configure Gitea backend"
@echo " issue-backend-set-gitea Configure Forgejo backend (Gitea-compatible API)"
@echo ""
@echo "Synchronization:"
@echo " issue-sync Sync with remote backend"

View file

@ -1,10 +1,10 @@
# Issue Core — External Tracker Connector
**A backend-agnostic connector to third-party issue trackers (Gitea/Forgejo, GitHub, …), not the fleet's internal work-origin or coordination substrate.**
**A backend-agnostic connector to third-party issue trackers (Forgejo, GitHub, …), not the fleet's internal work-origin or coordination substrate.**
## Purpose
**issue-core** is a standardized abstraction layer for talking to external issue-tracking backends (Gitea, GitHub, GitLab, local SQLite cache). Instead of each agent or automation implementing platform-specific APIs, they use one consistent CLI and Python interface — and, going forward, a durable **work-record UUID ↔ external issue id** mapping when a tracker projection is switched on.
**issue-core** is a standardized abstraction layer for talking to external issue-tracking backends (Forgejo, GitHub, GitLab, local SQLite cache). Instead of each agent or automation implementing platform-specific APIs, they use one consistent CLI and Python interface — and, going forward, a durable **work-record UUID ↔ external issue id** mapping when a tracker projection is switched on. The self-hosted forge is **Forgejo** only (ACT-ADR-005); the HTTP API is Gitea-compatible.
### Fleet coordination vs external trackers
@ -32,7 +32,7 @@ For agent coordination *inside* the Coulomb fleet, use work records + state-hub,
✅ **Fully Implemented:**
- Complete CRUD operations (issues, labels, users, milestones, comments)
- Gitea backend (production-ready with full API integration)
- Forgejo backend (Gitea-compatible API; production-ready)
- Local SQLite backend (offline work with sync capability)
- CLI with JSON output for machine parsing
- Python API for programmatic access
@ -72,15 +72,20 @@ Clients authenticate with `Authorization: Bearer <key>` or `X-API-Key: <key>`.
See `SCOPE.md` "TaskSpec payload" for the request schema, or visit
`http://<host>:<port>/docs` once the server is running for live OpenAPI docs.
Internal scheduled automation (FI/Binky, mail, …) does **not** poll this
server. Claim/execute is activity-core **`ops_run`** (ACT-ADR-005).
`POST /issues/` is external tracker create/link only.
### Configuration (One-Time Setup)
**For Gitea-backed projects:**
**For Forgejo-backed projects:**
```bash
# Set your Gitea token
export GITEA_API_TOKEN="your-token-here"
# Preferred token name (CLI also accepts FORGEJO_API_TOKEN)
export FORGEJO_TOKEN="your-token-here"
# Deprecated aliases still work: GITEA_API_TOKEN, GITEA_TOKEN
# Configure backend
# Configure backend (type string `gitea` = Forgejo Gitea-compatible API)
issue backend add myproject gitea
# Prompts for: URL, owner, repo (reads token from environment)
@ -185,7 +190,7 @@ for issue in issues:
┌───────┴────────┐
│ │
┌──────▼─────┐ ┌──────▼──────┐
│ Local │ │ Gitea
│ Local │ │ Forgejo
│ (SQLite) │ │ (REST API) │
└────────────┘ └─────────────┘
```
@ -200,7 +205,7 @@ for issue in issues:
| Backend | Status | Features |
|---------|--------|----------|
| **Gitea** | ✅ Production | Full API, rate limiting, state mapping |
| **Forgejo** (Gitea-compatible API) | ✅ Production | Full API, rate limiting, state mapping |
| **Local SQLite** | ✅ Production | Offline work, fast queries, sync support |
| **GitHub** | 🚧 Planned (v1.1) | Full API integration |
| **GitLab** | 🚧 Planned (v1.2) | Full API integration |
@ -209,7 +214,7 @@ for issue in issues:
### 1. External tracker ops (when a backend is in use)
Agents or humans work issues that already live in Gitea/GitHub (e.g. OSS
Agents or humans work issues that already live in Forgejo/GitHub (e.g. OSS
inbound, customer Jira, or an intentional projection):
```bash
@ -227,7 +232,7 @@ Fleet-internal task claim/execute should use workplan task status in the repo
### 2. Human collaboration through a tracker UI
When the counterparty only uses GitHub/Gitea, project or update the external
When the counterparty only uses GitHub/Forgejo, project or update the external
issue and keep comments there. Mapping design (UUID ↔ external id) will tie
that projection back to the work record — see `docs/uuid-external-id-mapping.md`.
@ -238,14 +243,14 @@ Work offline with local backend, sync when online:
```bash
# Setup local backup
issue backend add backup local
issue sync pull gitea-production backup
issue sync pull forgejo-production backup
# Work offline
issue backend set-default backup
issue create "Offline tracker note" --label=offline
# Sync back
issue sync push backup gitea-production
issue sync push backup forgejo-production
```
## CLI Commands Reference
@ -318,7 +323,7 @@ issue-core/
│ │ ├── models.py # Issue, Label, User, etc.
│ │ └── interfaces.py # IssueBackend, SyncableBackend
│ ├── backends/
│ │ ├── gitea/ # Gitea backend implementation
│ │ ├── gitea/ # Forgejo backend (Gitea-compatible API)
│ │ └── local/ # SQLite backend implementation
│ └── cli/ # Click-based CLI
│ ├── commands.py # Issue operations
@ -358,7 +363,7 @@ issue map detach --uuid <work-record-uuid>
```
Mappings live in `~/.config/issue-tracker/mappings.db` (bookkeeping), independent
of whether CRUD targets Gitea or local SQLite.
of whether CRUD targets Forgejo or local SQLite.
## Roadmap (summary)
@ -412,7 +417,7 @@ The Issue Core is designed to be extensible:
- [ ] Integration tests with mock API
- [ ] Documentation
See existing backends (Gitea, Local) as reference implementations.
See existing backends (Forgejo / `gitea` module, Local) as reference implementations.
## Why "Facade" / connector?
@ -421,7 +426,7 @@ The **Facade Pattern** still describes the *implementation* style:
> *"Provide a unified interface to a set of interfaces in a subsystem."*
> — Gang of Four, Design Patterns
Instead of agents learning different APIs for GitHub (`gh`), GitLab (`glab`), Gitea, JIRA, etc., they use one consistent interface. The facade does not replace issue trackers — and it does **not** replace work records as the origin of fleet work. It connects the fleet to trackers at the boundary.
Instead of agents learning different APIs for GitHub (`gh`), GitLab (`glab`), Forgejo, JIRA, etc., they use one consistent interface. The facade does not replace issue trackers — and it does **not** replace work records as the origin of fleet work. It connects the fleet to trackers at the boundary.
## License

View file

@ -28,7 +28,7 @@ replacing workplans/tasks.
✅ **Complete:**
- Core CRUD operations (100%)
- Gitea backend (production-ready)
- Forgejo backend (Gitea-compatible API; production-ready)
- Local SQLite backend (fully functional)
- CLI with JSON output
- Python programmatic API
@ -80,8 +80,8 @@ def detect_git_remote() -> Optional[Dict[str, str]]:
Returns:
{
'platform': 'gitea' | 'github' | 'gitlab',
'base_url': 'https://gitea.example.com',
'platform': 'forgejo' | 'github' | 'gitlab',
'base_url': 'https://forgejo.example.com',
'owner': 'myorg',
'repo': 'myproject'
}
@ -90,15 +90,15 @@ def detect_git_remote() -> Optional[Dict[str, str]]:
def parse_remote_url(url: str) -> Optional[Dict[str, str]]:
"""
Parse various git remote URL formats:
- https://gitea.example.com/owner/repo.git
- git@gitea.example.com:owner/repo.git
- https://forgejo.example.com/owner/repo.git
- git@forgejo.example.com:owner/repo.git
- https://github.com/owner/repo
"""
```
**Tests:** `tests/test_detection.py`
- Test various URL formats (HTTPS, SSH, with/without .git)
- Test platform detection (Gitea, GitHub, GitLab)
- Test platform detection (Forgejo, GitHub, GitLab)
- Test edge cases (subgroups, custom domains)
**Effort:** 2-3 days
@ -112,7 +112,8 @@ def parse_remote_url(url: str) -> Optional[Dict[str, str]]:
def load_backend_from_env() -> Optional[Dict[str, Any]]:
"""
Load backend config from environment variables:
- GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO
- FORGEJO_URL, FORGEJO_TOKEN, FORGEJO_OWNER, FORGEJO_REPO
(deprecated aliases: GITEA_URL, GITEA_TOKEN / GITEA_API_TOKEN, …)
- GITHUB_TOKEN (with auto-detection)
- GITLAB_URL, GITLAB_TOKEN
"""
@ -149,10 +150,10 @@ issue config auto
{
"backend": {
"type": "gitea",
"url": "https://gitea.example.com",
"url": "https://forgejo.example.com",
"owner": "myorg",
"repo": "myproject",
"token_source": "env:GITEA_TOKEN" // or "file:/path/to/token"
"token_source": "env:FORGEJO_TOKEN" // or "file:/path/to/token"; GITEA_* deprecated
},
"sync": {
"enabled": true,
@ -247,7 +248,7 @@ if issue backend show "$backend_name" &>/dev/null; then
fi
# Offer existing values as defaults
read -p "Gitea URL [$CURRENT_URL]: " url
read -p "Forgejo URL [$CURRENT_URL]: " url
url="${url:-$CURRENT_URL}"
read -p "Repository owner [$CURRENT_OWNER]: " owner
@ -403,7 +404,7 @@ class LockManager:
**Storage:** Store claims in issue metadata or separate tracking table.
**For Gitea backend:**
**For Forgejo backend:**
```json
// In issue.sync_metadata
{
@ -788,7 +789,7 @@ def sync_with_strategy(
### Phase 1 Success
- [ ] Agent can attach to any repo's tracker with zero manual config
- [ ] Environment-only setup works: `GITEA_TOKEN=xxx issue list`
- [ ] Environment-only setup works: `FORGEJO_TOKEN=xxx issue list`
- [ ] Auto-detection accuracy: >95% for common platforms
### Phase 1.5 Success (mapping)

View file

@ -55,7 +55,7 @@ A **Capability Implementation** is a concrete realization of a Capability Family
**Characteristics:**
- Provides **concrete functionality** (code, CLI, API)
- May implement **multiple backend variants** (Gitea, GitHub, local)
- May implement **multiple backend variants** (Forgejo, GitHub, local)
- Has **maturity levels** (experimental, beta, production)
- Can be **composed with other capabilities**
@ -255,7 +255,7 @@ metadata:
implementation: issue-core
version: 1.0.0
description: >
Unified interface for issue tracking across Gitea, GitHub, GitLab.
Unified interface for issue tracking across Forgejo, GitHub, GitLab.
Enables agent coordination via standardized issue operations.
purpose:
@ -315,7 +315,7 @@ purpose:
- "Unified issue management for polyglot platform environments"
problems_solved:
- "Direct API calls to GitHub/GitLab/Gitea (avoids credential sprawl)"
- "Direct API calls to GitHub/GitLab/Forgejo (avoids credential sprawl)"
- "Inconsistent issue tracking access patterns"
- "Platform-specific code in agents"
- "Offline/online workflow synchronization"
@ -323,7 +323,7 @@ purpose:
# When to use this capability
usage_rules:
MUST_USE_INSTEAD_OF:
- "Direct Gitea/GitHub/GitLab API calls"
- "Direct Forgejo/GitHub/GitLab API calls"
- "Platform-specific CLIs (gh, glab)"
- "Python libraries (PyGithub, python-gitlab)"
@ -361,7 +361,7 @@ integration:
required: true
method: manual # auto-detection planned for v1.1
steps:
- "Export GITEA_API_TOKEN environment variable"
- "Export FORGEJO_TOKEN environment variable (GITEA_API_TOKEN is a deprecated alias)"
- "Run: issue backend add myproject gitea"
- "Provide: URL, owner, repo when prompted"
- "Run: issue backend set-default myproject"
@ -416,8 +416,8 @@ efficiency:
credentials:
method: environment_variables
variables:
- GITEA_API_TOKEN
- GITEA_URL # optional with config
- FORGEJO_TOKEN # preferred; GITEA_API_TOKEN is a deprecated alias
- FORGEJO_URL # optional with config; GITEA_URL deprecated alias
security:
- "Tokens never in code or logs"
@ -736,7 +736,7 @@ metadata:
```
Project A needs GitHub issues
Project B needs GitLab issues
Project C needs Gitea issues
Project C needs Forgejo issues
→ Pattern: "We need issue tracking"
→ Family: "issue-tracking"

View file

@ -25,7 +25,7 @@ aspirational lists when deciding “does issue-core already do X?”
| Concept | Notes |
| --- | --- |
| `Issue` | id, number, title, description, state, timestamps, labels, assignees, milestone, comments, backend_id/type, `sync_metadata` |
| `IssueState` | `open`, `closed`, `in_progress`, `blocked` (Gitea maps non-closed → open on the wire) |
| `IssueState` | `open`, `closed`, `in_progress`, `blocked` (Forgejo Gitea-compatible API maps non-closed → open on the wire) |
| `Label` / priority / type | Priority and type are primarily **label conventions** (`priority:high`, `bug`, …), not separate backend columns everywhere |
| `User`, `Milestone`, `Comment` | First-class models on the backend interface |
| `Priority`, `IssueType` enums | Helpers for label-derived classification |
@ -46,7 +46,7 @@ aspirational lists when deciding “does issue-core already do X?”
| Backend | Module | Role |
| --- | --- | --- |
| **Local SQLite** | `issue_core.backends.local` | Offline store; hard delete supported; bulk ops; sync-capable |
| **Gitea** | `issue_core.backends.gitea` | Remote REST; **Forgejo-compatible** in railiance01 deploy; no true delete (close-as-delete); rate-limit aware; sync-capable |
| **Forgejo** | `issue_core.backends.gitea` | Remote REST via **Forgejo (Gitea-compatible API)**; backend type string remains `gitea`. Gitea is not a second product. No true delete (close-as-delete); rate-limit aware; sync-capable |
**Not implemented as code:** GitHub, GitLab, Jira backends (optional deps may be
declared in `pyproject.toml`; no backend package under `issue_core/backends/`).
@ -64,7 +64,9 @@ declared in `pyproject.toml`; no backend package under `issue_core/backends/`).
- JSON-friendly output for agents (`--format=json` on list/show paths).
- Backend configs: `~/.config/issue-tracker/` (default backend + named configs).
- Mapping store: `~/.config/issue-tracker/mappings.db` (independent of CRUD backend).
- Gitea token typically from env (`GITEA_API_TOKEN` / config); never commit secrets.
- Forgejo token from env: **`FORGEJO_TOKEN`** (preferred) or `FORGEJO_API_TOKEN`;
legacy **`GITEA_API_TOKEN` / `GITEA_TOKEN`** are deprecated aliases the CLI
still accepts. Never commit secrets.
### 1.4b Work-record mapping (shipped v0.2.x)
@ -103,7 +105,7 @@ also `sync_metadata.mapping` and a row in `mappings.db`.
### 1.6 Synchronization (CLI)
- Bidirectional pull/push between configured backends (e.g. Gitea ↔ local).
- Bidirectional pull/push between configured backends (e.g. Forgejo ↔ local).
- Uses `get_issues_modified_since` / conflict hooks where backends implement them.
- Conflict handling is basic (operator / force flags) — not a full merge engine.
@ -111,7 +113,7 @@ also `sync_metadata.mapping` and a row in `mappings.db`.
| Artifact | Provides |
| --- | --- |
| PyPI package | Built wheel/sdist; Makefile targets publish to Coulomb Gitea/Forgejo registry |
| PyPI package | Built wheel/sdist; Makefile targets publish to Coulomb Forgejo registry |
| Docker image | Built and published by this app repo; production consumes an immutable digest |
| `rapp-issue-core` | Authoritative railiance01 runtime package: manifests, private Service, ExternalSecret references, NetworkPolicy, rollout, rollback, and live evidence |
| `docs/package-release.md` | Release notes for packaging |
@ -148,7 +150,7 @@ Things this repo **owns** and may grow, consistent with the connector role.
backends when tracker use is intentional.
- List and filter (state, labels, assignee, milestone, text search as backends allow).
- Label, assignee, and milestone management through the backend interface.
- Delete where the backend allows (SQLite hard delete; Gitea effectively close).
- Delete where the backend allows (SQLite hard delete; Forgejo effectively close).
### 2.2 Connector / mapping (owned direction)
@ -164,7 +166,9 @@ Things this repo **owns** and may grow, consistent with the connector role.
### 2.3 Surfaces
- CLI for humans and agents on a shell.
- REST for intentional automation and worker claim/list/close.
- REST for intentional **external tracker** create/link and worker
list/update/close of tracker issues. This is **not** the fleet ops claim
queue (ACT-ADR-005 / activity-core `ops_run`).
- Python library for embedding.
- Optional future NATS consumer **only** for intentional external projection
(never silent fleet work origin).
@ -204,6 +208,24 @@ activity-core (and peers) must not treat `POST /issues/` as the always-on
default for every matched rule. Emitters own that policy; issue-core keeps
accepting authenticated POSTs without advertising itself as a landing zone.
Follow-up: `activity-core` **ACTIVITY-WP-0022** (from ISSUE-WP-0004-T05).
Default activity-core sink remains **state-hub progress + `ops_run`**, not
REST to issue-core for Binky/FI.
### 3.3b Ops claim queue (ACT-ADR-005) — not this repo
Internal scheduled automation **claim/execute** belongs to activity-core
**`ops_run`** ([ACT-ADR-005](../activity-core/docs/adr/adr-005-ops-runs-vs-dev-work-records.md)).
issue-core does **not** provide the fleet ops claim queue. Do not poll
`GET /issues/` as “the automation queue.”
ACT-ADR-005: *issue-cores correct role is a connector facade over
external trackers (Forgejo, GitHub, Jira, …). It is not the origin of
work records and not the default internal ops queue. Gitea is out of
scope for this fleet; the self-hosted forge is Forgejo.*
`POST /issues/` = external tracker create/link only. rein-aharness
`poll --source=issue-core` is **legacy / external ticket** path only
(see ISSUE-WP-0006, REINAH-WP-0005-T06).
### 3.4 Project management
@ -246,8 +268,8 @@ Emitters record who/what spawned an external issue (e.g. activity-core
| --- | --- | --- |
| Human / agent shell | CLI | Tracker admin; `project` / `map` for work-record links |
| Library consumers | Python API + `MappingService` | Same backends + mapping store without shell |
| Workers (e.g. harness) | REST GET/PATCH | List / claim / close external issues when in the loop |
| activity-core IssueSink | REST POST | **Optional, intentional** external issues only — not default for internal findings; may pass `work_record_uuid` |
| Workers (e.g. harness) | REST GET/PATCH | List / update / close **external tracker** issues when that tracker is in use — **not** the fleet ops claim queue |
| activity-core IssueSink | REST POST | **Optional, intentional** external issues only — not default for internal findings or Binky/FI; may pass `work_record_uuid`. Default sink is state-hub progress + `ops_run` (ACT-ADR-005) |
| Mapping-aware emitters | CLI or POST `/issues/` with UUID | Project/link work-record UUID ↔ external id |
### 4.3 TaskSpec contract (`POST /issues/`)
@ -287,14 +309,16 @@ Retained for intentional emits and backward compatibility:
```
`issue_id` is the **backend** issue identity for the emitters log; not a
work-record UUID. (`github` appears in the response enum for forward
compatibility; no GitHub backend is shipped yet.)
work-record UUID. The wire value `gitea` is the Forgejo (Gitea-compatible
API) backend type string — not a second Gitea product. (`github` appears in
the response enum for forward compatibility; no GitHub backend is shipped
yet.)
### 4.4 Credential routing (operators / agents)
| Need | Owner |
| --- | --- |
| Gitea/Forgejo API token for backend | OpenBao / operator path (`warden route`) |
| Forgejo API token for backend (`FORGEJO_TOKEN`; legacy `GITEA_*` alias) | OpenBao / operator path (`warden route`) |
| `ISSUE_CORE_API_KEY` for REST | Shared secret via deploy secrets (e.g. ExternalSecret) |
| SSH certs | ops-warden only |
@ -309,7 +333,7 @@ issue_core/
core/ # models, IssueBackend ABC, factory, MappingService
backends/
local/ # SQLite issue store
gitea/ # Gitea/Forgejo REST
gitea/ # Forgejo REST (Gitea-compatible API)
cli/ # Click: issue, project, map, backend, sync, serve
api/ # FastAPI: ingest + query (+ auth, schemas)
tests/
@ -326,7 +350,7 @@ tests, image construction, and Forgejo image publication.
| Language | Python 3.8+ |
| CLI | Click |
| HTTP | FastAPI + Pydantic v2 + uvicorn (`[api]` extra) |
| HTTP client (Gitea) | requests |
| HTTP client (Forgejo) | requests |
| Issue store (local) | SQLite (`issues.db`) |
| Mapping store | SQLite (`mappings.db`, separate file) |
| Tests | pytest |
@ -350,3 +374,5 @@ tests, image construction, and Forgejo image publication.
(issue-core issues = external projections only)
- `the-custodian/research/WorkOrchestrationArchitectureDraft.md` §4.2
- activity-core IssueSink / **ACTIVITY-WP-0022** — emitter-side default policy
- activity-core **ACT-ADR-005**`ops_run` is the fleet ops claim queue; not issue-core
- `workplans/ISSUE-WP-0006-forgejo-only-projection-boundary.md` — Forgejo-only + projection boundary

View file

@ -2,9 +2,10 @@
# Render issue-core backends.json from environment, then start the API.
#
# The backend structure (host/owner/repo/default) is non-secret and supplied
# via the BACKENDS_TEMPLATE env (a ConfigMap), with the Gitea token injected
# from GITEA_BACKEND_TOKEN (an ExternalSecret-materialized Secret). The token
# is never baked into the image or committed to Git.
# via the BACKENDS_TEMPLATE env (a ConfigMap), with the Forgejo token injected
# from FORGEJO_BACKEND_TOKEN (preferred) or GITEA_BACKEND_TOKEN (deprecated
# alias; ExternalSecret-materialized Secret). The token is never baked into
# the image or committed to Git.
set -eu
CONFIG_DIR="${HOME}/.config/issue-tracker"
@ -14,12 +15,13 @@ mkdir -p "${CONFIG_DIR}"
# Substitute the token placeholder using python (always present in the image)
# to avoid shell-escaping issues with the secret value.
FORGEJO_BACKEND_TOKEN="${FORGEJO_BACKEND_TOKEN:-}" \
GITEA_BACKEND_TOKEN="${GITEA_BACKEND_TOKEN:-}" \
BACKENDS_TEMPLATE="${BACKENDS_TEMPLATE}" \
python - "${CONFIG_DIR}/backends.json" <<'PY'
import json, os, sys
tmpl = json.loads(os.environ["BACKENDS_TEMPLATE"])
token = os.environ.get("GITEA_BACKEND_TOKEN", "")
token = os.environ.get("FORGEJO_BACKEND_TOKEN") or os.environ.get("GITEA_BACKEND_TOKEN", "")
for cfg in tmpl.values():
if isinstance(cfg, dict) and cfg.get("token") == "__FROM_ENV__":
cfg["token"] = token

View file

@ -21,7 +21,7 @@
| Side | Key |
| --- | --- |
| Work record | **UUIDv7** (bookkeeping); canonical id optional denorm for UX |
| External | `(backend, external_id)` where backend is `sqlite` \| `gitea` \| … |
| External | `(backend, external_id)` where backend is `sqlite` \| `gitea` (Forgejo) \| … |
## Outward status table (task kind + pass-through)

View file

@ -87,7 +87,7 @@ without naming UUIDv7 or the human-facing id.
**Why it matters:** Mapping implementation and CLI (`issue project
ISSUE-WP-…`) will almost always start from the **canonical name**; storage
key should be UUID. INTENTs UUID-only phrasing can steer readers to hide
canonical ids or treat Gitea numbers as peer identity to human task names.
canonical ids or treat Forgejo numbers as peer identity to human task names.
**Recommendation:** In INTENT “Why / Mapping”, say:
@ -215,7 +215,7 @@ Framework makes `lane` mandatory on every work record; tags and budgets are
normative.
INTENT is silent — **acceptable** if projections never invent parallel
lane/budget models on issues. Soft risk if Gitea labels start carrying a
lane/budget models on issues. Soft risk if Forgejo labels start carrying a
second autonomy model (`lane:green` as issue labels without mapping rules).
**Recommendation:** Mapping/projection: lane, tags, budget stay on the work
@ -231,7 +231,7 @@ INTENTs “What it is” lists responsibilities that mix **shipped** and
| Responsibility in INTENT | Shipped? |
| --- | --- |
| Projection / CRUD on backends | **Yes** (SQLite, Gitea) |
| Projection / CRUD on backends | **Yes** (SQLite, Forgejo) |
| Mapping UUID ↔ external id | **No** — design only |
| Two-way boundary sync | **No** — CLI backend sync ≠ work-record boundary sync |
| CLI + REST | **Yes** |
@ -295,7 +295,7 @@ defaults, rewriting AGENT_INTEGRATION examples — those are separate work.
| Connector role | Yes | Yes | Yes |
| Not work origin | Yes | §3.1 | Yes |
| Mapping | Target | Design + not shipped | Yes if INTENT softens “What it is” |
| Backends | SQLite + Gitea (+ planned) | Same | Yes |
| Backends | SQLite + Forgejo (+ planned) | Same | Yes |
| REST TaskSpec | Optional intentional | Full contract | Yes |
| State-hub runtime events from package | Downstream mention | Explicitly not shipped | Mild aspirational gap in older SCOPE; current SCOPE honest |

View file

@ -15,7 +15,7 @@ make package-check
Publish to the Coulomb organization registry:
```bash
TWINE_USERNAME=<gitea-user> \
TWINE_USERNAME=<forgejo-user> \
TWINE_PASSWORD=<package-token> \
make publish-forgejo
```

View file

@ -11,13 +11,13 @@ kinds in `the-custodian/canon/standards/work-record-types_v0.1.md`
## Problem
When collaboration requires a third-party tracker (Forgejo/Gitea, GitHub,
When collaboration requires a third-party tracker (Forgejo, GitHub,
Jira, …), the fleet still needs a stable link between:
| Side | Identity today | Owner |
| --- | --- | --- |
| Internal work record | UUIDv7 written back as `state_hub_*_id` (workplan/task/intake/…) | repo file + state-hub |
| External issue | Backend issue id / number (+ URL) | Gitea, GitHub, … via issue-core |
| External issue | Backend issue id / number (+ URL) | Forgejo, GitHub, … via issue-core |
Without an explicit mapping:
@ -95,7 +95,7 @@ mapping:
work_record_uuid: <uuidv7> # primary internal key
work_record_id: "ISSUE-WP-0004-T01" # canonical name, optional denorm
work_record_kind: task # task | intake | workplan | …
backend: gitea # gitea | github | gitlab | jira | sqlite
backend: gitea # forgejo product; type string `gitea` | github | gitlab | jira | sqlite
external_id: "176" # backend-native issue id/number
external_url: "https://…/issues/176"
target_repo: "coulomb/example" # optional routing hint
@ -123,7 +123,7 @@ issue-core only:
```yaml
# on a task / intake YAML block (illustrative — exact key names TBD with canon)
external_tracker:
backend: gitea
backend: forgejo # product; stored type may be `gitea`
issue_id: "176"
issue_url: "https://…"
mapped_by: issue-core
@ -132,7 +132,7 @@ external_tracker:
Alternatively a single string field if canon prefers flat keys:
```text
issue_core_external_ref: "gitea:coulomb/example#176"
issue_core_external_ref: "forgejo:coulomb/example#176"
```
Canon PR owns the field name; issue-core owns the mapping authority.
@ -142,7 +142,7 @@ Canon PR owns the field name; issue-core owns the mapping authority.
Recommended v1:
1. **Local SQLite** table `work_record_issue_map` (even when primary CRUD
backend is Gitea) — mapping is fleet bookkeeping, not a Gitea concept.
backend is Forgejo) — mapping is fleet bookkeeping, not a Forgejo concept.
2. **Issue.sync_metadata.mapping** (or top-level) echo for convenience when
the issue is loaded:
```json
@ -154,7 +154,7 @@ Recommended v1:
}
}
```
3. Optional Gitea label / body footer for human visibility in the tracker UI
3. Optional Forgejo label / body footer for human visibility in the tracker UI
(non-authoritative).
### API / CLI (sketch only)
@ -255,7 +255,7 @@ increment. Until enabled, connector load is zero beyond idle process cost.
documenting multi-tracker projection is future work.
- **SQLite-only offline:** mapping table lives with local DB; sync of
issues and mappings to remote is coordinated (mapping rows are not
pushed as Gitea native objects).
pushed as Forgejo native objects).
## Implementation sketch (later workplan)

View file

@ -12,7 +12,8 @@ This directory contains working examples of autonomous agents using the Issue Co
2. **Configure backend** (one-time setup):
```bash
export GITEA_API_TOKEN="your-token"
export FORGEJO_TOKEN="your-token"
# Deprecated alias: GITEA_API_TOKEN
issue backend add myproject gitea
# Enter: URL, owner, repo when prompted
issue backend set-default myproject
@ -20,10 +21,11 @@ This directory contains working examples of autonomous agents using the Issue Co
3. **Set environment variables** for scripts:
```bash
export GITEA_URL=https://gitea.example.com
export GITEA_TOKEN=your-token
export GITEA_OWNER=your-org
export GITEA_REPO=your-repo
export FORGEJO_URL=https://forgejo.example.com
export FORGEJO_TOKEN=your-token
export FORGEJO_OWNER=your-org
export FORGEJO_REPO=your-repo
# Deprecated aliases: GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO
```
## Examples
@ -212,7 +214,7 @@ issue backend add myproject gitea
### "Authentication failed"
```bash
# Check token is valid
curl -H "Authorization: token $GITEA_TOKEN" $GITEA_URL/api/v1/user
curl -H "Authorization: token $FORGEJO_TOKEN" $FORGEJO_URL/api/v1/user
```
### "No issues found"

View file

@ -9,10 +9,10 @@ This agent demonstrates agent-human collaboration:
4. Agent can ask questions and wait for answers
Usage:
export GITEA_URL=https://gitea.example.com
export GITEA_TOKEN=your-token
export GITEA_OWNER=your-org
export GITEA_REPO=your-repo
export FORGEJO_URL=https://forgejo.example.com
export FORGEJO_TOKEN=your-token
export FORGEJO_OWNER=your-org
export FORGEJO_REPO=your-repo
python human_in_loop.py
"""
@ -40,11 +40,11 @@ class HumanInLoopAgent:
self.backend = None
def connect(self):
"""Connect to backend."""
base_url = os.environ['GITEA_URL']
token = os.environ['GITEA_TOKEN']
owner = os.environ['GITEA_OWNER']
repo = os.environ['GITEA_REPO']
"""Connect to Forgejo backend."""
base_url = os.environ.get('FORGEJO_URL') or os.environ['GITEA_URL']
token = os.environ.get('FORGEJO_TOKEN') or os.environ.get('FORGEJO_API_TOKEN') or os.environ['GITEA_TOKEN']
owner = os.environ.get('FORGEJO_OWNER') or os.environ['GITEA_OWNER']
repo = os.environ.get('FORGEJO_REPO') or os.environ['GITEA_REPO']
self.backend = GiteaBackend()
self.backend.connect({

View file

@ -9,10 +9,10 @@ This agent monitors issue health and sends alerts:
- Reports on project velocity and bottlenecks
Usage:
export GITEA_URL=https://gitea.example.com
export GITEA_TOKEN=your-token
export GITEA_OWNER=your-org
export GITEA_REPO=your-repo
export FORGEJO_URL=https://forgejo.example.com
export FORGEJO_TOKEN=your-token
export FORGEJO_OWNER=your-org
export FORGEJO_REPO=your-repo
python monitoring_agent.py [--stale-days=7] [--check-interval=3600]
"""
@ -42,11 +42,11 @@ class MonitoringAgent:
self.backend = None
def connect(self):
"""Connect to backend."""
base_url = os.environ['GITEA_URL']
token = os.environ['GITEA_TOKEN']
owner = os.environ['GITEA_OWNER']
repo = os.environ['GITEA_REPO']
"""Connect to Forgejo backend."""
base_url = os.environ.get('FORGEJO_URL') or os.environ['GITEA_URL']
token = os.environ.get('FORGEJO_TOKEN') or os.environ.get('FORGEJO_API_TOKEN') or os.environ['GITEA_TOKEN']
owner = os.environ.get('FORGEJO_OWNER') or os.environ['GITEA_OWNER']
repo = os.environ.get('FORGEJO_REPO') or os.environ['GITEA_REPO']
self.backend = GiteaBackend()
self.backend.connect({

View file

@ -11,10 +11,10 @@ Demonstrates a CI/CD-like pipeline with multiple specialized agents:
Each agent monitors for issues in their stage and advances them through the pipeline.
Usage:
export GITEA_URL=https://gitea.example.com
export GITEA_TOKEN=your-token
export GITEA_OWNER=your-org
export GITEA_REPO=your-repo
export FORGEJO_URL=https://forgejo.example.com
export FORGEJO_TOKEN=your-token
export FORGEJO_OWNER=your-org
export FORGEJO_REPO=your-repo
# Run all agents in parallel (in separate terminals)
python multi_agent_pipeline.py --agent=coder
@ -50,11 +50,11 @@ class BaseAgent:
self.backend = None
def connect(self):
"""Connect to backend from environment."""
base_url = os.environ['GITEA_URL']
token = os.environ['GITEA_TOKEN']
owner = os.environ['GITEA_OWNER']
repo = os.environ['GITEA_REPO']
"""Connect to Forgejo backend from environment."""
base_url = os.environ.get('FORGEJO_URL') or os.environ['GITEA_URL']
token = os.environ.get('FORGEJO_TOKEN') or os.environ.get('FORGEJO_API_TOKEN') or os.environ['GITEA_TOKEN']
owner = os.environ.get('FORGEJO_OWNER') or os.environ['GITEA_OWNER']
repo = os.environ.get('FORGEJO_REPO') or os.environ['GITEA_REPO']
self.backend = GiteaBackend()
self.backend.connect({

View file

@ -9,10 +9,10 @@ This agent demonstrates a basic workflow:
4. Report completion and close the issue
Usage:
export GITEA_URL=https://gitea.example.com
export GITEA_TOKEN=your-token
export GITEA_OWNER=your-org
export GITEA_REPO=your-repo
export FORGEJO_URL=https://forgejo.example.com
export FORGEJO_TOKEN=your-token
export FORGEJO_OWNER=your-org
export FORGEJO_REPO=your-repo
python simple_task_executor.py
"""
@ -39,16 +39,22 @@ class SimpleTaskExecutor:
self.backend = None
def connect(self):
"""Connect to Gitea backend from environment variables."""
base_url = os.environ.get('GITEA_URL')
token = os.environ.get('GITEA_TOKEN')
owner = os.environ.get('GITEA_OWNER')
repo = os.environ.get('GITEA_REPO')
"""Connect to Forgejo backend from environment variables."""
base_url = os.environ.get('FORGEJO_URL') or os.environ.get('GITEA_URL')
token = (
os.environ.get('FORGEJO_TOKEN')
or os.environ.get('FORGEJO_API_TOKEN')
or os.environ.get('GITEA_TOKEN')
or os.environ.get('GITEA_API_TOKEN')
)
owner = os.environ.get('FORGEJO_OWNER') or os.environ.get('GITEA_OWNER')
repo = os.environ.get('FORGEJO_REPO') or os.environ.get('GITEA_REPO')
if not all([base_url, token, owner, repo]):
raise ValueError(
"Missing required environment variables: "
"GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO"
"FORGEJO_URL, FORGEJO_TOKEN, FORGEJO_OWNER, FORGEJO_REPO "
"(deprecated aliases: GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO)"
)
self.backend = GiteaBackend()

View file

@ -1,9 +1,10 @@
schema_version: open-reuse.integration.v0.1
id: issue-core-gitea
name: issue-core Gitea Backend
name: issue-core Forgejo Backend
description: >
Pluggable remote backend that maps the issue-core unified issue model onto the
Gitea issues API for external-tracker projection and synchronization.
Forgejo issues API (Gitea-compatible) for external-tracker projection and
synchronization. Gitea is not a second supported product.
status: registered
owner: issue-core
@ -13,9 +14,9 @@ local:
system: issue-core
upstream:
name: Gitea
project_url: https://github.com/go-gitea/gitea
homepage: https://about.gitea.com/
name: Forgejo (Gitea-compatible API)
project_url: https://codeberg.org/forgejo/forgejo
homepage: https://forgejo.org/
version_policy: gitea-api-v1
monitor:
releases: true
@ -29,14 +30,14 @@ reuse:
- plugin
risk_level: medium
rationale: >
Gitea REST API is wrapped behind the RemoteBackend interface; local task
lifecycle semantics remain stable across backend swaps.
Forgejo (Gitea-compatible) REST API is wrapped behind the RemoteBackend
interface; local task lifecycle semantics remain stable across backend swaps.
boundary:
type: adapter
local_adapter: issue_core.backends.gitea.backend.GiteaBackend
local_interface: issue_core.core.interfaces.RemoteBackend
reused_surface: Gitea /api/v1 issues, labels, milestones, comments
reused_surface: Forgejo (Gitea-compatible) /api/v1 issues, labels, milestones, comments
contracts:
- issue-core.backend.v1
fragility_points:

View file

@ -2,12 +2,12 @@
issue-core external issue-tracker connector
Backend-agnostic CLI, library, and optional REST surface for operating on
third-party issue trackers (Gitea/Forgejo, local SQLite). Not the origin of
fleet work records (see INTENT.md / SCOPE.md).
third-party issue trackers (Forgejo via Gitea-compatible API, local SQLite).
Not the origin of fleet work records (see INTENT.md / SCOPE.md).
Shipped:
- Unified issue model and IssueBackend plugin architecture
- Local SQLite + Gitea backends; bidirectional sync CLI
- Local SQLite + Forgejo backends; bidirectional sync CLI
- CLI (`issue` / `issue-core`) and optional FastAPI REST (`issue serve`)
"""

View file

@ -7,5 +7,5 @@ API regardless of the underlying issue tracking system.
Available Backends:
- local: SQLite-based local backend for offline use
- gitea: Gitea API backend for GitHub-compatible systems
- gitea: Forgejo backend (Gitea-compatible API)
"""

View file

@ -1,12 +1,11 @@
"""
Gitea Backend
Forgejo backend (Gitea-compatible API).
A backend implementation for Gitea issue tracking systems.
This backend provides integration with Gitea API for remote issue management.
Talks to the self-hosted Forgejo HTTP API. Gitea is not a second supported
product. The backend type string remains `gitea`.
Features:
- Full Gitea API integration
- GitHub-compatible operations
- Full Forgejo / Gitea-compatible API integration
- Remote synchronization
- Authentication support
- Rate limiting compliance

View file

@ -1,8 +1,9 @@
"""
Gitea Backend Implementation
Forgejo backend (Gitea-compatible API).
Provides integration with Gitea API for remote issue tracking.
This backend adapts the Gitea API to our unified issue model.
Talks to the self-hosted Forgejo HTTP API, which is Gitea-compatible.
Gitea is not a second supported product. The backend type string remains
`gitea` for compatibility.
"""
import requests
@ -16,7 +17,7 @@ from ...core.models import Issue, Label, User, Milestone, Comment, IssueState, P
class GiteaAPIError(Exception):
"""Gitea API specific errors."""
"""Forgejo (Gitea-compatible API) errors."""
pass
@ -26,7 +27,7 @@ class GiteaRateLimitError(GiteaAPIError):
class GiteaBackend(RemoteBackend, SyncableBackend):
"""Gitea API backend for remote issue tracking."""
"""Forgejo backend using the Gitea-compatible HTTP API."""
def __init__(self):
self.base_url: Optional[str] = None
@ -44,7 +45,7 @@ class GiteaBackend(RemoteBackend, SyncableBackend):
supports_webhooks=True,
supports_real_time=False,
max_labels_per_issue=None,
max_assignees_per_issue=10 # Gitea typical limit
max_assignees_per_issue=10 # Forgejo / Gitea-compatible typical limit
)
@property
@ -56,7 +57,7 @@ class GiteaBackend(RemoteBackend, SyncableBackend):
return self._capabilities
def connect(self, config: Dict[str, Any]) -> None:
"""Connect to Gitea API."""
"""Connect to Forgejo (Gitea-compatible API)."""
self.base_url = config['base_url'].rstrip('/')
self.token = config['token']
self.owner = config['owner']
@ -71,10 +72,10 @@ class GiteaBackend(RemoteBackend, SyncableBackend):
# Test connection
if not self.test_connection():
raise GiteaAPIError("Failed to connect to Gitea API")
raise GiteaAPIError("Failed to connect to Forgejo (Gitea-compatible API)")
def disconnect(self) -> None:
"""Disconnect from Gitea API."""
"""Disconnect from Forgejo."""
self.session.close()
self.base_url = None
self.token = None
@ -195,20 +196,20 @@ class GiteaBackend(RemoteBackend, SyncableBackend):
if issue.milestone:
data['milestone'] = int(issue.milestone.backend_id) if issue.milestone.backend_id else None
# Gitea expects numeric label IDs on issue create/update. Name-only
# labels are preserved in issue-core metadata but omitted from the API
# payload until a label-resolution step exists.
label_ids = []
for label in issue.labels:
if not label.backend_id:
continue
try:
label_ids.append(int(label.backend_id))
except (TypeError, ValueError):
continue
if label_ids:
data['labels'] = label_ids
# Gitea expects numeric label IDs on issue create/update. Name-only
# labels are preserved in issue-core metadata but omitted from the API
# payload until a label-resolution step exists.
label_ids = []
for label in issue.labels:
if not label.backend_id:
continue
try:
label_ids.append(int(label.backend_id))
except (TypeError, ValueError):
continue
if label_ids:
data['labels'] = label_ids
return data
# Issue CRUD Operations

View file

@ -12,6 +12,23 @@ from .utils import (
echo_error, echo_warning, echo_info, confirm_action
)
# Preferred FORGEJO_*; GITEA_* remain deprecated aliases (ISSUE-WP-0006).
_FORGE_TOKEN_ENV_NAMES = (
'FORGEJO_TOKEN',
'FORGEJO_API_TOKEN',
'GITEA_API_TOKEN',
'GITEA_TOKEN',
)
def _forge_token_from_env():
"""Return (env_name, token) for the first set Forgejo/legacy token var."""
for name in _FORGE_TOKEN_ENV_NAMES:
value = os.getenv(name)
if value:
return name, value
return None, None
@click.group()
def backend_group():
@ -46,14 +63,19 @@ def add_backend(ctx, name, backend_type):
'db_path': str(db_path)
}
elif backend_type == 'gitea':
base_url = click.prompt('Gitea base URL (e.g., https://git.example.com)')
base_url = click.prompt('Forgejo base URL (Gitea-compatible API)')
owner = click.prompt('Repository owner/organization')
repo = click.prompt('Repository name')
# Check for API token in environment variable first
env_token = os.getenv('GITEA_API_TOKEN')
env_name, env_token = _forge_token_from_env()
if env_token:
click.echo(f"Using API token from GITEA_API_TOKEN environment variable")
if env_name in ('GITEA_API_TOKEN', 'GITEA_TOKEN'):
click.echo(
f"Using API token from {env_name} "
"(deprecated alias; prefer FORGEJO_TOKEN)"
)
else:
click.echo(f"Using API token from {env_name} environment variable")
token = env_token
else:
token = click.prompt('Access token', hide_input=True)

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "issue-core"
description = "External issue-tracker connector (Gitea/Forgejo, SQLite) with work-record UUID mapping — not the fleet work origin"
description = "External issue-tracker connector (Forgejo, SQLite) with work-record UUID mapping — not the fleet work origin"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}

View file

@ -1,7 +1,7 @@
---
id: capability.infotech.issue-tracking
name: External Issue Tracker Connector
summary: Backend-agnostic CLI/Python connector to Gitea/Forgejo (and SQLite cache)
summary: Backend-agnostic CLI/Python connector to Forgejo (and SQLite cache)
with work-record UUID ↔ external issue mapping. Not the fleet work origin.
owner: issue-core
status: draft
@ -29,10 +29,10 @@ external_evidence:
confidence: medium
basis: scope_vs_intent_and_consumer_expectations
satisfied_expectations:
- Gitea + local SQLite backends
- Forgejo + local SQLite backends
- CLI with JSON output; project/map commands
- work_record_uuid optional on TaskSpec
- credential handling via env vars (GITEA_API_TOKEN, ISSUE_CORE_API_KEY)
- credential handling via env vars (FORGEJO_TOKEN preferred; GITEA_API_TOKEN deprecated alias; ISSUE_CORE_API_KEY)
broken_expectations: []
out_of_scope_expectations:
- Fleet task origin / intake promotion (work-record canon)

View file

@ -4,7 +4,7 @@ domain: helix_forge
capabilities:
- id: capability.infotech.issue-tracking
name: Universal Issue Tracking Coordination
summary: Unified Python/CLI interface for issue tracking across Gitea, GitHub, and GitLab, preventing
summary: Unified Python/CLI interface for issue tracking across Forgejo, GitHub, and GitLab, preventing
direct platform API usage and credential sprawl for coordinating agents.
vector: D4 / A2 / C2 / R1
domain: infotech

View file

@ -53,7 +53,7 @@ class TestCLICommands:
], input='https://git.example.com\ntestorg\ntestrepo\n')
assert result.exit_code == 0
assert 'Using API token from GITEA_API_TOKEN environment variable' in result.output
assert 'Using API token from FORGEJO_TOKEN environment variable' in result.output
assert 'Backend \'test-gitea\' added successfully' in result.output
# Verify save_backend_configs was called with correct data
@ -205,8 +205,13 @@ class TestEnvironmentTokenDetection:
@patch('os.getenv')
def test_gitea_token_detection(self, mock_getenv):
"""Test GITEA_API_TOKEN environment variable detection."""
mock_getenv.return_value = 'test-env-token'
"""Deprecated GITEA_API_TOKEN alias is still accepted."""
def _getenv(name, default=None):
if name == 'GITEA_API_TOKEN':
return 'test-env-token'
return default
mock_getenv.side_effect = _getenv
from issue_core.cli.backend_commands import add_backend
@ -220,4 +225,5 @@ class TestEnvironmentTokenDetection:
], input='https://git.example.com\ntestorg\ntestrepo\n')
assert result.exit_code == 0
assert 'Using API token from GITEA_API_TOKEN environment variable' in result.output
assert 'Using API token from GITEA_API_TOKEN' in result.output
assert 'deprecated alias; prefer FORGEJO_TOKEN' in result.output

View file

@ -77,7 +77,7 @@ class TestGiteaBackend:
backend = GiteaBackend()
backend.session = mock_session
with pytest.raises(GiteaAPIError, match="Failed to connect to Gitea API"):
with pytest.raises(GiteaAPIError, match="Failed to connect to Forgejo"):
backend.connect(self.test_config)
def test_url_construction_fix(self):
@ -98,29 +98,29 @@ class TestGiteaBackend:
called_url = mock_request.call_args[1]['url'] if 'url' in mock_request.call_args[1] else mock_request.call_args[0][1]
assert called_url == 'https://git.example.com/api/v1/repos/owner/repo'
def test_gitea_payload_omits_name_only_labels(self):
"""Gitea issue payloads only include numeric label IDs."""
now = datetime.now(timezone.utc)
issue = Issue(
id="",
number=0,
title="Test issue",
description="Test description",
state=IssueState.OPEN,
created_at=now,
updated_at=now,
labels=[
Label(name="priority:low"),
Label(name="source:rule", backend_id="not-a-number"),
Label(name="existing", backend_id="42"),
],
)
payload = self.backend._unified_issue_to_gitea(issue)
assert payload["labels"] == [42]
assert payload["title"] == "Test issue"
def test_gitea_payload_omits_name_only_labels(self):
"""Gitea issue payloads only include numeric label IDs."""
now = datetime.now(timezone.utc)
issue = Issue(
id="",
number=0,
title="Test issue",
description="Test description",
state=IssueState.OPEN,
created_at=now,
updated_at=now,
labels=[
Label(name="priority:low"),
Label(name="source:rule", backend_id="not-a-number"),
Label(name="existing", backend_id="42"),
],
)
payload = self.backend._unified_issue_to_gitea(issue)
assert payload["labels"] == [42]
assert payload["title"] == "Test issue"
@patch('issue_core.backends.gitea.backend.requests.Session')
def test_test_connection_success(self, mock_session_class):
"""Test test_connection method works correctly."""

View file

@ -49,8 +49,8 @@ be an alternative origin.
This workplan brings issue-core's own docs, scope, and (where cheap) code
in line with that decision. It does not remove issue-core's existing
capability — Gitea/GitHub backend CRUD stays useful — it retargets *what
issue-core is for*.
capability — Forgejo/GitHub backend CRUD stays useful — it retargets *what
issue-core is for*. (Product language: Forgejo only; see ISSUE-WP-0006.)
## Task: Rewrite INTENT.md — connector, not landing zone

View file

@ -35,7 +35,7 @@ Close the gap between:
| Layer | State at start of this WP |
| --- | --- |
| **INTENT** | Work-record-aligned connector (2026-07-22) |
| **SCOPE §1 shipped** | Models, SQLite/Gitea, CLI, REST TaskSpec, backend sync |
| **SCOPE §1 shipped** | Models, SQLite/Forgejo, CLI, REST TaskSpec, backend sync |
| **SCOPE §1.8 / target** | Mapping store/API, boundary sync, work_record_uuid, dual-lifecycle rules |
Deliver a **minimum viable connector core**: durable mapping + project/link
@ -137,14 +137,14 @@ state_hub_task_id: "027871b0-696d-5ab7-b18c-b26167e97813"
Per `docs/uuid-external-id-mapping.md`:
- Table `work_record_issue_map` (or equivalent) on local backend / dedicated
store used even when CRUD backend is Gitea
store used even when CRUD backend is Forgejo
- Uniqueness: active `(backend, external_id)` and active
`(work_record_uuid, backend)`
- Domain service `MappingService`: upsert, resolve by UUID, by canonical id
(if provided), by external id, detach
- Unit tests for idempotent project and uniqueness
**Acceptance:** Tests pass without a live Gitea; store survives reconnect;
**Acceptance:** Tests pass without a live Forgejo; store survives reconnect;
SCOPE §1.8 “mapping store” can move toward shipped with an honest partial
note until CLI/API land.
@ -208,7 +208,7 @@ Implement **outward-only** application of T03 rules for mapped records
- Do **not** write work-record files
- Inward sync remains out of scope for this task (document as next)
**Acceptance:** Policy tests + one integration-style test on local/Gitea mock;
**Acceptance:** Policy tests + one integration-style test on local/Forgejo mock;
SCOPE distinguishes backend sync vs work-record boundary sync clearly.
## Task: SCOPE inventory refresh after implementation

View file

@ -4,18 +4,20 @@ type: workplan
title: "Forgejo-only forge + projection boundary (not ops queue)"
domain: infotech
repo: issue-core
status: ready
status: finished
owner: grok
topic_slug: issue-core
priority: medium
created: "2026-08-03"
updated: "2026-08-03"
updated: "2026-09-14"
depends_on: []
related:
- ACT-ADR-005
- ACTIVITY-WP-0022
- ISSUE-WP-0004
- ISSUE-WP-0005
- REIN-A-0002
- REINAH-WP-0005
state_hub_workstream_id: "4d465264-cbe1-56ba-84ed-bd580b649b76"
---
@ -41,7 +43,7 @@ and so rein-aharness stops treating issue-core as the primary automation queue
```task
id: ISSUE-WP-0006-T01
status: todo
status: done
priority: high
state_hub_task_id: "927eee42-e22d-5c0d-a4f8-dcd36c53c8e9"
```
@ -65,7 +67,7 @@ compatibility notes remain.
```task
id: ISSUE-WP-0006-T02
status: todo
status: done
priority: high
state_hub_task_id: "a1d69a48-833b-5e5e-8286-14f5aba627f3"
```
@ -87,7 +89,7 @@ Explicit section:
```task
id: ISSUE-WP-0006-T03
status: todo
status: done
priority: medium
state_hub_task_id: "75eef8a3-abd7-50dd-9171-3488fda84da5"
```
@ -107,7 +109,7 @@ as the primary automation loop.
```task
id: ISSUE-WP-0006-T04
status: todo
status: done
priority: low
state_hub_task_id: "5be0bce4-1816-599f-a98b-4fa1565a8b0e"
```
@ -117,14 +119,32 @@ No code change required in issue-core if rein owns the doc cut.
**Done when:** cross-link in ISSUE-WP-0006 and REIN-A-0002.
**Closed 2026-09-14:** REIN-A-0002 was completed as **REINAH-WP-0005**. T06
there already marked issue-core poll as legacy/external. Cross-links added:
- this workplan `related: REIN-A-0002`, `REINAH-WP-0005`
- `~/rein-aharness/workplans/REINAH-WP-0005-ops-run-claim-loop.md` T06 note
+ `related: ISSUE-WP-0006`
- `~/rein-aharness/docs/task-intake.md` (legacy/external section)
- State Hub message to `rein-aharness`
## Acceptance
- [ ] No Gitea-as-product planning
- [ ] Projection boundary clear
- [ ] Ops claim queue explicitly out of scope for issue-core
- [x] No Gitea-as-product planning
- [x] Projection boundary clear
- [x] Ops claim queue explicitly out of scope for issue-core
## Out of scope
- Implementing ops_run (ACTIVITY-WP-0026)
- Fixing Forgejo PAT for rest sink (only if projection needs it later)
- GitHub/Jira connectors
- Implementing ops_run (ACTIVITY-WP-0026) — lives in activity-core / REINAH-WP-0005
- Fixing Forgejo PAT for rest sink (only if projection needs it later) — ISSUE-WP-0003 residual / deploy
- GitHub/Jira connectors — product growth, not this workplan
No new residual: those items already have live work records elsewhere.
## Close note (2026-09-14)
Product language is **Forgejo** only. The HTTP API is Gitea-compatible;
module `issue_core.backends.gitea` and backend type `gitea` remain as
identifiers. `FORGEJO_TOKEN` is preferred; `GITEA_*` is a deprecated alias.
INTENT/SCOPE quote ACT-ADR-005: issue-core is not the ops claim queue.