docs(ISSUE-WP-0006): Forgejo-only language and projection boundary
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:
parent
f9d276dadf
commit
ee9b85215d
34 changed files with 410 additions and 263 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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/"
|
||||
|
|
|
|||
51
INTENT.md
51
INTENT.md
|
|
@ -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-core’s 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.
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -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"
|
||||
|
|
|
|||
39
README.md
39
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
25
ROADMAP.md
25
ROADMAP.md
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
54
SCOPE.md
54
SCOPE.md
|
|
@ -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-core’s 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 emitter’s 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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. INTENT’s 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 @@ INTENT’s “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 |
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
"""
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue