generated from coulomb/repo-seed
feat: rename to issue-core and add task ingestion endpoint
Renames the package, distribution, CLI alias, Makefile targets, and working directory from issue-facade to issue-core, signalling its role as the authoritative task lifecycle manager for the Coulomb org (peer to activity-core, rules-core, project-core). Adds POST /issues/ ingestion endpoint for activity-core's IssueSink, under a new optional [api] extra. The endpoint is served by `issue serve`, authenticates via the ISSUE_CORE_API_KEY env var (Bearer or X-API-Key header), and routes the TaskSpec payload to the configured default backend with full traceability metadata embedded in sync_metadata. - T01: Python package issue_tracker -> issue_core, dir rename - T02: registered in state hub under custodian domain - T03: INTENT.md (what it is, what it isn't, how it fits) - T04: SCOPE.md (in/out-of-scope, integration boundaries) - T05: POST /issues/ via FastAPI + Uvicorn, 9 unit tests - T06: docs/nats-task-ingestion.md design stub Closes ISSC-WP-0001. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Capability Bootstrap System
|
||||
|
||||
**How coding agents discover and integrate the issue-facade capability.**
|
||||
**How coding agents discover and integrate the issue-core capability.**
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
@@ -44,7 +44,7 @@ Comprehensive guide for coding agents:
|
||||
- Error handling
|
||||
- Examples
|
||||
|
||||
**Injected into:** `.claude/capabilities/issue-facade.md` in main project
|
||||
**Injected into:** `.claude/capabilities/issue-core.md` in main project
|
||||
|
||||
### 3. Integration Automation
|
||||
|
||||
@@ -61,7 +61,7 @@ Interactive script that:
|
||||
```bash
|
||||
make integrate
|
||||
# or
|
||||
cd capabilities/issue-facade && ./.capability/integrate.sh
|
||||
cd capabilities/issue-core && ./.capability/integrate.sh
|
||||
```
|
||||
|
||||
### 4. Integration Checklist
|
||||
@@ -81,7 +81,7 @@ Step-by-step checklist for humans integrating the capability:
|
||||
|
||||
```
|
||||
Main Project Setup
|
||||
├── 1. Human runs: cd capabilities/issue-facade && make integrate
|
||||
├── 1. Human runs: cd capabilities/issue-core && make integrate
|
||||
├── 2. Script installs capability
|
||||
├── 3. Script configures backend (prompts for credentials)
|
||||
├── 4. Script copies agent-context.md → .claude/capabilities/
|
||||
@@ -101,13 +101,13 @@ Main Project Setup
|
||||
Agent Workflow
|
||||
├── 1. Agent receives task involving issues
|
||||
├── 2. Agent checks .claude/capabilities/ for relevant docs
|
||||
├── 3. Agent finds issue-facade.md with comprehensive guide
|
||||
├── 3. Agent finds issue-core.md with comprehensive guide
|
||||
├── 4. Agent uses Python API or CLI as documented
|
||||
└── 5. Agent avoids direct API calls (warned in docs)
|
||||
```
|
||||
|
||||
**Key Files Agent Reads:**
|
||||
- `.claude/capabilities/issue-facade.md` - Complete usage guide
|
||||
- `.claude/capabilities/issue-core.md` - Complete usage guide
|
||||
- `.claude/context/capabilities.md` - High-level capability list
|
||||
- `.claude/commands/use-issues.md` - Slash command for context injection
|
||||
|
||||
@@ -116,18 +116,18 @@ Agent Workflow
|
||||
```
|
||||
project-root/
|
||||
├── capabilities/
|
||||
│ └── issue-facade/ # Capability code
|
||||
│ └── issue-core/ # Capability code
|
||||
│ ├── CAPABILITY-issue-tracking.yaml # Machine-readable metadata
|
||||
│ ├── .capability/
|
||||
│ │ ├── agent-context.md # Agent guide (source)
|
||||
│ │ ├── integrate.sh # Integration script
|
||||
│ │ └── README.md # This file
|
||||
│ ├── issue_tracker/ # Python package
|
||||
│ ├── issue_core/ # Python package
|
||||
│ └── ...
|
||||
│
|
||||
├── .claude/ # Claude Code configuration
|
||||
│ ├── capabilities/ # Capability docs for agents
|
||||
│ │ └── issue-facade.md # Agent guide (copy)
|
||||
│ │ └── issue-core.md # Agent guide (copy)
|
||||
│ │
|
||||
│ ├── commands/ # Slash commands
|
||||
│ │ └── use-issues.md # /use-issues command
|
||||
@@ -135,7 +135,7 @@ project-root/
|
||||
│ └── context/ # Always-available context
|
||||
│ └── capabilities.md # List of all capabilities
|
||||
│
|
||||
└── .issue-facade/ # Capability config (gitignored)
|
||||
└── .issue-core/ # Capability config (gitignored)
|
||||
├── config.json # Backend configuration
|
||||
└── issues.db # Local cache/backup
|
||||
```
|
||||
@@ -150,13 +150,13 @@ import os
|
||||
from pathlib import Path
|
||||
|
||||
def has_issue_capability(project_root: Path) -> bool:
|
||||
"""Check if issue-facade capability is available."""
|
||||
capability_guide = project_root / ".claude/capabilities/issue-facade.md"
|
||||
"""Check if issue-core capability is available."""
|
||||
capability_guide = project_root / ".claude/capabilities/issue-core.md"
|
||||
return capability_guide.exists()
|
||||
|
||||
if has_issue_capability(Path.cwd()):
|
||||
# Use capability
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
backend = GiteaBackend()
|
||||
else:
|
||||
# Fall back or prompt human
|
||||
@@ -173,15 +173,15 @@ def get_capability_docs(capability_name: str) -> str:
|
||||
return None
|
||||
|
||||
# Agent can read and understand the guide
|
||||
docs = get_capability_docs("issue-facade")
|
||||
docs = get_capability_docs("issue-core")
|
||||
# Parse docs for API usage patterns...
|
||||
```
|
||||
|
||||
**3. Use the API as documented:**
|
||||
```python
|
||||
# Example from agent-context.md
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
backend = GiteaBackend()
|
||||
backend.connect(config)
|
||||
@@ -204,8 +204,8 @@ response = requests.post(
|
||||
**Good (Uses capability):**
|
||||
```python
|
||||
# ✅ Uses capability
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, IssueState
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, IssueState
|
||||
from datetime import datetime, timezone
|
||||
|
||||
backend = GiteaBackend()
|
||||
@@ -302,7 +302,7 @@ make integrate
|
||||
make discover-capabilities
|
||||
# Output:
|
||||
# Found capabilities:
|
||||
# - issue-facade (v1.0.0) - Issue tracking coordination
|
||||
# - issue-core (v1.0.0) - Issue tracking coordination
|
||||
# - ... (other capabilities)
|
||||
|
||||
# Auto-integrate all
|
||||
@@ -318,7 +318,7 @@ Capabilities have priority scores (0-100) indicating importance:
|
||||
- **50-69 (Medium):** Use when available
|
||||
- **Below 50 (Low):** Optional convenience
|
||||
|
||||
**issue-facade priority: 95 (Critical)**
|
||||
**issue-core priority: 95 (Critical)**
|
||||
|
||||
Agents should check priority when deciding whether to use a capability or fall back to alternatives.
|
||||
|
||||
@@ -367,7 +367,7 @@ Agents should check priority when deciding whether to use a capability or fall b
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why not just document "use issue-facade" in README?**
|
||||
**Q: Why not just document "use issue-core" in README?**
|
||||
A: Agents often skip general docs. Putting it in `.claude/capabilities/` makes it part of their working context.
|
||||
|
||||
**Q: What if agent bypasses capability anyway?**
|
||||
@@ -405,10 +405,10 @@ response = requests.post(gitea_url + "/issues", ...)
|
||||
|
||||
**With capability (properly integrated):**
|
||||
```python
|
||||
# Agent checks .claude/capabilities/issue-facade.md
|
||||
# Agent checks .claude/capabilities/issue-core.md
|
||||
# Reads: "Use this API, don't use direct requests"
|
||||
# Agent follows documented pattern:
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
backend = GiteaBackend()
|
||||
backend.connect(config)
|
||||
backend.create_issue(issue)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Issue Facade - Agent Integration Context
|
||||
# Issue Core - Agent Integration Context
|
||||
|
||||
**🤖 For Coding Agents: Read this to understand how to use issue tracking in this project.**
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
# Verify installation
|
||||
issue --version
|
||||
# or
|
||||
python -c "from issue_tracker.backends.gitea import GiteaBackend; print('OK')"
|
||||
python -c "from issue_core.backends.gitea import GiteaBackend; print('OK')"
|
||||
```
|
||||
|
||||
### Basic Usage (Python)
|
||||
|
||||
```python
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, IssueState, User, Comment
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, IssueState, User, Comment
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
from datetime import datetime, timezone
|
||||
import os
|
||||
|
||||
@@ -228,7 +228,7 @@ if not verify_issue_backend():
|
||||
## Error Handling
|
||||
|
||||
```python
|
||||
from issue_tracker.backends.gitea.backend import GiteaAPIError
|
||||
from issue_core.backends.gitea.backend import GiteaAPIError
|
||||
|
||||
try:
|
||||
issue = backend.get_issue_by_number(42)
|
||||
@@ -317,7 +317,7 @@ If you're unsure whether to use this capability for something:
|
||||
- **NO** → You can use other methods
|
||||
|
||||
**Example:**
|
||||
- "Create an issue for the bug I found" → **Use issue-facade**
|
||||
- "Read the project README" → Don't need issue-facade
|
||||
- "Check if issue #42 exists" → **Use issue-facade**
|
||||
- "Clone the repository" → Don't need issue-facade
|
||||
- "Create an issue for the bug I found" → **Use issue-core**
|
||||
- "Read the project README" → Don't need issue-core
|
||||
- "Check if issue #42 exists" → **Use issue-core**
|
||||
- "Clone the repository" → Don't need issue-core
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Integration script for issue-facade capability
|
||||
# Integration script for issue-core capability
|
||||
# This script helps the main project discover and integrate the capability
|
||||
|
||||
set -e
|
||||
|
||||
CAPABILITY_NAME="issue-facade"
|
||||
CAPABILITY_NAME="issue-core"
|
||||
CAPABILITY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$CAPABILITY_DIR/../.." && pwd)}"
|
||||
|
||||
echo "🔧 Issue Facade Capability Integration"
|
||||
echo "🔧 Issue Core Capability Integration"
|
||||
echo " Capability: $CAPABILITY_DIR"
|
||||
echo " Project: $PROJECT_ROOT"
|
||||
echo ""
|
||||
@@ -90,7 +90,7 @@ case $choice in
|
||||
echo "📝 Adding to Claude Code context..."
|
||||
mkdir -p "$PROJECT_ROOT/.claude/capabilities"
|
||||
cp "$CAPABILITY_DIR/.capability/agent-context.md" \
|
||||
"$PROJECT_ROOT/.claude/capabilities/issue-facade.md"
|
||||
"$PROJECT_ROOT/.claude/capabilities/issue-core.md"
|
||||
|
||||
# Create or update context file
|
||||
CONTEXT_FILE="$PROJECT_ROOT/.claude/context/capabilities.md"
|
||||
@@ -102,10 +102,10 @@ case $choice in
|
||||
|
||||
This project uses specialized capabilities. Always check for existing capabilities before implementing similar functionality.
|
||||
|
||||
## Issue Tracking: issue-facade
|
||||
## Issue Tracking: issue-core
|
||||
|
||||
**Location:** `capabilities/issue-facade/`
|
||||
**Documentation:** `.claude/capabilities/issue-facade.md`
|
||||
**Location:** `capabilities/issue-core/`
|
||||
**Documentation:** `.claude/capabilities/issue-core.md`
|
||||
**Priority:** CRITICAL (always use for issue operations)
|
||||
|
||||
**MUST USE FOR:**
|
||||
@@ -120,13 +120,13 @@ This project uses specialized capabilities. Always check for existing capabiliti
|
||||
|
||||
**Quick Start:**
|
||||
```python
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
backend = GiteaBackend()
|
||||
backend.connect(config)
|
||||
issues = backend.list_issues()
|
||||
```
|
||||
|
||||
**Full Documentation:** See `.claude/capabilities/issue-facade.md`
|
||||
**Full Documentation:** See `.claude/capabilities/issue-core.md`
|
||||
EOF
|
||||
echo "✓ Created $CONTEXT_FILE"
|
||||
else
|
||||
@@ -138,7 +138,7 @@ EOF
|
||||
echo "✓ Added to Claude Code context"
|
||||
echo ""
|
||||
echo "Files created:"
|
||||
echo " - $PROJECT_ROOT/.claude/capabilities/issue-facade.md"
|
||||
echo " - $PROJECT_ROOT/.claude/capabilities/issue-core.md"
|
||||
echo " - $CONTEXT_FILE"
|
||||
;;
|
||||
|
||||
@@ -148,15 +148,15 @@ EOF
|
||||
mkdir -p "$PROJECT_ROOT/.claude/commands"
|
||||
|
||||
cat > "$PROJECT_ROOT/.claude/commands/use-issues.md" << 'EOF'
|
||||
You are working with issue tracking. Use the **issue-facade capability**:
|
||||
You are working with issue tracking. Use the **issue-core capability**:
|
||||
|
||||
## Available API
|
||||
|
||||
**Python (Recommended):**
|
||||
```python
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, IssueState
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, IssueState
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
backend = GiteaBackend()
|
||||
backend.connect(config)
|
||||
@@ -193,7 +193,7 @@ issue close 42 --comment="Fixed"
|
||||
|
||||
## Full Documentation
|
||||
|
||||
See `capabilities/issue-facade/AGENT_INTEGRATION.md` for:
|
||||
See `capabilities/issue-core/AGENT_INTEGRATION.md` for:
|
||||
- Complete API reference
|
||||
- Coordination patterns
|
||||
- Error handling
|
||||
@@ -206,7 +206,7 @@ EOF
|
||||
echo "Usage in Claude Code:"
|
||||
echo " /use-issues"
|
||||
echo ""
|
||||
echo "This will inject issue-facade context into the conversation."
|
||||
echo "This will inject issue-core context into the conversation."
|
||||
;;
|
||||
|
||||
5)
|
||||
@@ -250,7 +250,7 @@ EOF
|
||||
mkdir -p "$PROJECT_ROOT/.claude/context"
|
||||
|
||||
cp "$CAPABILITY_DIR/.capability/agent-context.md" \
|
||||
"$PROJECT_ROOT/.claude/capabilities/issue-facade.md"
|
||||
"$PROJECT_ROOT/.claude/capabilities/issue-core.md"
|
||||
|
||||
# Create context file if not exists
|
||||
CONTEXT_FILE="$PROJECT_ROOT/.claude/context/capabilities.md"
|
||||
@@ -258,21 +258,21 @@ EOF
|
||||
cat > "$CONTEXT_FILE" << 'EOF'
|
||||
# Available Capabilities
|
||||
|
||||
## Issue Tracking: issue-facade
|
||||
## Issue Tracking: issue-core
|
||||
|
||||
**CRITICAL:** Always use this for issue operations. Never bypass with direct API calls.
|
||||
|
||||
**Docs:** `.claude/capabilities/issue-facade.md`
|
||||
**Docs:** `.claude/capabilities/issue-core.md`
|
||||
**Usage:** `/use-issues`
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Create slash command
|
||||
cat > "$PROJECT_ROOT/.claude/commands/use-issues.md" << 'EOF'
|
||||
Use the issue-facade capability for all issue tracking operations.
|
||||
Use the issue-core capability for all issue tracking operations.
|
||||
|
||||
**Quick reference:** See `.claude/capabilities/issue-facade.md`
|
||||
**Examples:** See `capabilities/issue-facade/examples/agents/`
|
||||
**Quick reference:** See `.claude/capabilities/issue-core.md`
|
||||
**Examples:** See `capabilities/issue-core/examples/agents/`
|
||||
|
||||
**DO NOT use direct API calls or platform CLIs!**
|
||||
EOF
|
||||
@@ -285,7 +285,7 @@ EOF
|
||||
echo ""
|
||||
issue --version && echo "✓ CLI works" || echo "❌ CLI not working"
|
||||
issue backend list | grep -q "default" && echo "✓ Backend configured" || echo "⚠️ Backend not configured"
|
||||
[ -f "$PROJECT_ROOT/.claude/capabilities/issue-facade.md" ] && echo "✓ Claude context exists" || echo "❌ Claude context missing"
|
||||
[ -f "$PROJECT_ROOT/.claude/capabilities/issue-core.md" ] && echo "✓ Claude context exists" || echo "❌ Claude context missing"
|
||||
[ -f "$PROJECT_ROOT/.claude/commands/use-issues.md" ] && echo "✓ Slash command exists" || echo "❌ Slash command missing"
|
||||
|
||||
echo ""
|
||||
@@ -294,7 +294,7 @@ EOF
|
||||
echo "Next steps:"
|
||||
echo " 1. Test: issue list --limit=5"
|
||||
echo " 2. In Claude Code: /use-issues"
|
||||
echo " 3. See examples: capabilities/issue-facade/examples/agents/"
|
||||
echo " 3. See examples: capabilities/issue-core/examples/agents/"
|
||||
;;
|
||||
|
||||
0)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Issue Facade Integration Checklist
|
||||
# Issue Core Integration Checklist
|
||||
|
||||
**For project maintainers integrating this capability into their codebase.**
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
- [ ] **Install capability:**
|
||||
```bash
|
||||
pip install -e capabilities/issue-facade/
|
||||
pip install -e capabilities/issue-core/
|
||||
```
|
||||
|
||||
- [ ] **Verify installation:**
|
||||
@@ -42,8 +42,8 @@
|
||||
- [ ] **Copy agent context to project:**
|
||||
```bash
|
||||
mkdir -p .claude/capabilities/
|
||||
cp capabilities/issue-facade/.capability/agent-context.md \
|
||||
.claude/capabilities/issue-facade.md
|
||||
cp capabilities/issue-core/.capability/agent-context.md \
|
||||
.claude/capabilities/issue-core.md
|
||||
```
|
||||
|
||||
- [ ] **Add to Claude Code context:**
|
||||
@@ -53,17 +53,17 @@
|
||||
|
||||
This project uses specialized capabilities. Always check these before implementing similar functionality.
|
||||
|
||||
## Issue Tracking: issue-facade
|
||||
## Issue Tracking: issue-core
|
||||
|
||||
**Location:** `capabilities/issue-facade/`
|
||||
**Documentation:** `.claude/capabilities/issue-facade.md`
|
||||
**Location:** `capabilities/issue-core/`
|
||||
**Documentation:** `.claude/capabilities/issue-core.md`
|
||||
|
||||
**CRITICAL:** Always use this capability for issue operations. Never use:
|
||||
- Direct API calls (requests to /api/v1/repos/...)
|
||||
- Platform CLIs (gh, glab)
|
||||
- Platform libraries (PyGithub, python-gitlab)
|
||||
|
||||
See `.claude/capabilities/issue-facade.md` for usage patterns.
|
||||
See `.claude/capabilities/issue-core.md` for usage patterns.
|
||||
```
|
||||
|
||||
### Option 2: Slash Command
|
||||
@@ -71,11 +71,11 @@
|
||||
- [ ] **Create slash command:**
|
||||
Create `.claude/commands/use-issues.md`:
|
||||
```markdown
|
||||
You are working with issue tracking. Use the issue-facade capability:
|
||||
You are working with issue tracking. Use the issue-core capability:
|
||||
|
||||
**Python API:**
|
||||
```python
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
backend = GiteaBackend()
|
||||
backend.connect(config)
|
||||
```
|
||||
@@ -86,7 +86,7 @@
|
||||
issue create "Title" --label=bug
|
||||
```
|
||||
|
||||
**Full docs:** See `capabilities/issue-facade/AGENT_INTEGRATION.md`
|
||||
**Full docs:** See `capabilities/issue-core/AGENT_INTEGRATION.md`
|
||||
|
||||
**DO NOT use direct API calls or platform CLIs!**
|
||||
```
|
||||
@@ -106,7 +106,7 @@
|
||||
## Agent Configuration
|
||||
|
||||
- [ ] **Set agent identity:**
|
||||
Add to `.issue-facade/config.json`:
|
||||
Add to `.issue-core/config.json`:
|
||||
```json
|
||||
{
|
||||
"agent": {
|
||||
@@ -126,8 +126,8 @@
|
||||
|
||||
- [ ] **Test basic operations:**
|
||||
```python
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
backend = GiteaBackend()
|
||||
backend.connect({'base_url': '...', 'token': '...', 'owner': '...', 'repo': '...'})
|
||||
@@ -153,26 +153,26 @@
|
||||
```markdown
|
||||
## Issue Tracking
|
||||
|
||||
This project uses the issue-facade capability for unified issue tracking.
|
||||
This project uses the issue-core capability for unified issue tracking.
|
||||
|
||||
**Setup:**
|
||||
```bash
|
||||
pip install -e capabilities/issue-facade/
|
||||
pip install -e capabilities/issue-core/
|
||||
export GITEA_API_TOKEN="your-token"
|
||||
issue backend add myproject gitea
|
||||
```
|
||||
|
||||
**Usage:** See `capabilities/issue-facade/AGENT_INTEGRATION.md`
|
||||
**Usage:** See `capabilities/issue-core/AGENT_INTEGRATION.md`
|
||||
```
|
||||
|
||||
- [ ] **Add to CONTRIBUTING.md:**
|
||||
```markdown
|
||||
### Issue Tracking
|
||||
|
||||
Always use the `issue` command or Python API from `issue_tracker` package.
|
||||
Always use the `issue` command or Python API from `issue_core` package.
|
||||
Never make direct API calls to Gitea/GitHub/GitLab.
|
||||
|
||||
Examples: `capabilities/issue-facade/examples/agents/`
|
||||
Examples: `capabilities/issue-core/examples/agents/`
|
||||
```
|
||||
|
||||
## Security Review
|
||||
@@ -180,9 +180,9 @@
|
||||
- [ ] **Verify tokens are not in code:** `git grep GITEA_TOKEN` (should be empty)
|
||||
- [ ] **Check .gitignore includes:**
|
||||
```
|
||||
.issue-facade/config.json
|
||||
.issue-facade/issues.db
|
||||
.issue-facade/credentials.json
|
||||
.issue-core/config.json
|
||||
.issue-core/issues.db
|
||||
.issue-core/credentials.json
|
||||
```
|
||||
|
||||
- [ ] **Audit token permissions:** Read-only for bots, write for implementation
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
- [ ] **Run capability tests:**
|
||||
```bash
|
||||
cd capabilities/issue-facade/
|
||||
cd capabilities/issue-core/
|
||||
make test
|
||||
```
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
|
||||
- [ ] **Schedule regular updates:**
|
||||
```bash
|
||||
cd capabilities/issue-facade/
|
||||
cd capabilities/issue-core/
|
||||
git pull origin main
|
||||
pip install -e . --upgrade
|
||||
```
|
||||
@@ -232,7 +232,7 @@ If capability causes issues:
|
||||
|
||||
- [ ] **Keep backup config:**
|
||||
```bash
|
||||
cp ~/.config/issue-facade/backends.json ~/.config/issue-facade/backends.json.backup
|
||||
cp ~/.config/issue-core/backends.json ~/.config/issue-core/backends.json.backup
|
||||
```
|
||||
|
||||
- [ ] **Document rollback steps in project wiki/docs**
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Agent Integration Guide
|
||||
|
||||
**Issue Facade for Autonomous Coding Agent Coordination**
|
||||
**Issue Core for Autonomous Coding Agent Coordination**
|
||||
|
||||
## Purpose
|
||||
|
||||
The **Issue Facade** capability provides a standardized interface for autonomous coding agents to coordinate project implementation through issue tracking. Instead of agents directly interfacing with platform-specific APIs (GitHub, GitLab, Gitea), they use a unified abstraction that works consistently across backends.
|
||||
The **Issue Core** capability provides a standardized interface for autonomous coding agents to coordinate project implementation through issue tracking. Instead of agents directly interfacing with platform-specific APIs (GitHub, GitLab, Gitea), they use a unified abstraction that works consistently across backends.
|
||||
|
||||
### Why Issue Tracking for Agent Coordination?
|
||||
|
||||
@@ -67,7 +67,7 @@ Issue tracking provides a natural coordination mechanism for multi-agent softwar
|
||||
### 1. Installation
|
||||
|
||||
```bash
|
||||
cd capabilities/issue-facade
|
||||
cd capabilities/issue-core
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
@@ -99,7 +99,7 @@ issue backend set-default my-project
|
||||
# Configure local SQLite backend
|
||||
issue backend add local-work local
|
||||
# Prompts for:
|
||||
# - Database path: .issue-facade/issues.db
|
||||
# - Database path: .issue-core/issues.db
|
||||
|
||||
issue backend set-default local-work
|
||||
```
|
||||
@@ -178,8 +178,8 @@ issue list --label=reviewed --state=closed --format=json | \
|
||||
|
||||
```python
|
||||
# Agent creates implementation issues from requirements
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, IssueState
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, IssueState
|
||||
from datetime import datetime, timezone
|
||||
|
||||
backend = GiteaBackend()
|
||||
@@ -224,9 +224,9 @@ for task in subtasks:
|
||||
### Python Integration
|
||||
|
||||
```python
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, IssueState, User
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, IssueState, User
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
from datetime import datetime, timezone
|
||||
import os
|
||||
|
||||
@@ -271,7 +271,7 @@ created.state = IssueState.IN_PROGRESS
|
||||
backend.update_issue(created)
|
||||
|
||||
# Add comment
|
||||
from issue_tracker.core.models import Comment
|
||||
from issue_core.core.models import Comment
|
||||
comment = Comment(
|
||||
id=None,
|
||||
body="Analysis complete. Root cause: unclosed file handles in line 234",
|
||||
@@ -432,7 +432,7 @@ issue sync push backup gitea-remote
|
||||
|
||||
```python
|
||||
# Check for conflicts before sync
|
||||
from issue_tracker.cli.sync_commands import sync_pull
|
||||
from issue_core.cli.sync_commands import sync_pull
|
||||
|
||||
try:
|
||||
sync_pull(source='remote', target='local', dry_run=True)
|
||||
@@ -505,7 +505,7 @@ Create a setup script for each project:
|
||||
#!/bin/bash
|
||||
# setup-issue-tracking.sh
|
||||
|
||||
cat > .issue-facade-config << EOF
|
||||
cat > .issue-core-config << EOF
|
||||
GITEA_URL=https://gitea.example.com
|
||||
GITEA_OWNER=myorg
|
||||
GITEA_REPO=myproject
|
||||
@@ -513,7 +513,7 @@ GITEA_TOKEN_FILE=~/.secrets/gitea-token
|
||||
EOF
|
||||
|
||||
# Load config and configure backend
|
||||
source .issue-facade-config
|
||||
source .issue-core-config
|
||||
export GITEA_API_TOKEN=$(cat $GITEA_TOKEN_FILE)
|
||||
|
||||
issue backend add $(basename $(pwd)) gitea <<INPUT
|
||||
@@ -551,7 +551,7 @@ for issue_number in [1, 2, 3, 4, 5]:
|
||||
backend.update_issue(issue)
|
||||
|
||||
# GOOD: Use local backend for bulk operations
|
||||
from issue_tracker.backends.local import LocalSQLiteBackend
|
||||
from issue_core.backends.local import LocalSQLiteBackend
|
||||
|
||||
local = LocalSQLiteBackend()
|
||||
local.connect({'db_path': '/tmp/batch.db'})
|
||||
@@ -595,7 +595,7 @@ if time.time() - last_fetch > 60:
|
||||
### Phase 1: Auto-Configuration (v1.1)
|
||||
- Automatic git remote detection
|
||||
- Environment-variable-only setup
|
||||
- Per-repository `.issue-facade/config.json` support
|
||||
- Per-repository `.issue-core/config.json` support
|
||||
- `issue config detect` command
|
||||
|
||||
### Phase 2: Agent Features (v1.2)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Issue Facade Capability Manifest
|
||||
# Issue Core Capability Manifest
|
||||
# This file describes the capability to coding agents and integration systems
|
||||
|
||||
metadata:
|
||||
name: issue-facade
|
||||
name: issue-core
|
||||
version: 1.0.0
|
||||
type: coordination-tool
|
||||
description: >
|
||||
@@ -44,7 +44,7 @@ integration:
|
||||
methods:
|
||||
python_api:
|
||||
available: true
|
||||
import: "from issue_tracker.backends.gitea import GiteaBackend"
|
||||
import: "from issue_core.backends.gitea import GiteaBackend"
|
||||
docs: "AGENT_INTEGRATION.md"
|
||||
|
||||
cli:
|
||||
@@ -59,7 +59,7 @@ integration:
|
||||
|
||||
installation:
|
||||
method: pip
|
||||
command: "pip install -e capabilities/issue-facade/"
|
||||
command: "pip install -e capabilities/issue-core/"
|
||||
verify: "issue --version"
|
||||
|
||||
configuration:
|
||||
@@ -119,8 +119,8 @@ credentials:
|
||||
|
||||
security:
|
||||
- "Tokens never in code or logs"
|
||||
- "Config stored in ~/.config/issue-facade/"
|
||||
- "Per-repo config in .issue-facade/ (gitignored)"
|
||||
- "Config stored in ~/.config/issue-core/"
|
||||
- "Per-repo config in .issue-core/ (gitignored)"
|
||||
|
||||
best_practices:
|
||||
- "Use read-only tokens for monitoring agents"
|
||||
@@ -131,8 +131,8 @@ credentials:
|
||||
agent_guidance:
|
||||
quick_start: |
|
||||
# For Python agents:
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
backend = GiteaBackend()
|
||||
backend.connect(config)
|
||||
@@ -177,7 +177,7 @@ feedback:
|
||||
archived: "Resolved or outdated feedback"
|
||||
|
||||
for_users: |
|
||||
Submit feedback about issue-facade:
|
||||
Submit feedback about issue-core:
|
||||
./.capability/feedback submit "Feedback text"
|
||||
./.capability/feedback submit detailed-feedback.md
|
||||
|
||||
@@ -261,7 +261,7 @@ support:
|
||||
solution: "Check GITEA_API_TOKEN is set and valid"
|
||||
|
||||
- problem: "Command not found: issue"
|
||||
solution: "Run: pip install -e capabilities/issue-facade/"
|
||||
solution: "Run: pip install -e capabilities/issue-core/"
|
||||
|
||||
# Integration priority score (higher = more important for agent to use)
|
||||
priority:
|
||||
|
||||
@@ -85,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Enhanced agent integration documentation
|
||||
|
||||
### Fixed
|
||||
- ID mapping bugs in issue-facade
|
||||
- ID mapping bugs in issue-core
|
||||
- Sync metadata handling
|
||||
- Backend initialization edge cases
|
||||
|
||||
@@ -103,7 +103,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [0.1.0] - 2024-10-06
|
||||
|
||||
### Added
|
||||
- Initial extraction of issue-facade as standalone capability
|
||||
- Initial extraction of issue-core as standalone capability
|
||||
- Core CRUD operations (create, read, update, delete issues)
|
||||
- Gitea backend implementation (production-ready)
|
||||
- Local SQLite backend (offline capability)
|
||||
@@ -173,6 +173,6 @@ To submit feedback, see [feedback/README.md](feedback/README.md).
|
||||
|
||||
## Links
|
||||
|
||||
- [Repository](http://92.205.130.254:32166/coulomb/issue-facade)
|
||||
- [Issues](http://92.205.130.254:32166/coulomb/issue-facade/issues)
|
||||
- [Repository](http://92.205.130.254:32166/coulomb/issue-core)
|
||||
- [Issues](http://92.205.130.254:32166/coulomb/issue-core/issues)
|
||||
- [MarkiTect Project](https://github.com/markitect)
|
||||
|
||||
46
CLAUDE.md
46
CLAUDE.md
@@ -4,28 +4,28 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
Issue Facade is a universal CLI for issue tracking that provides a unified interface to multiple issue tracking backends (GitHub, GitLab, Gitea, local SQLite). It implements the **Facade Pattern** to abstract away differences between various issue tracking systems, providing developers with a consistent CLI experience regardless of the underlying backend.
|
||||
Issue Core is a universal CLI for issue tracking that provides a unified interface to multiple issue tracking backends (GitHub, GitLab, Gitea, local SQLite). It implements the **Facade Pattern** to abstract away differences between various issue tracking systems, providing developers with a consistent CLI experience regardless of the underlying backend.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Installation & Setup
|
||||
- Install for development: `pip install -e ".[dev]"`
|
||||
- Install production: `pip install -e .`
|
||||
- Clean build artifacts: `make issue-facade-clean`
|
||||
- Clean build artifacts: `make issue-core-clean`
|
||||
|
||||
### Testing
|
||||
- Run all tests: `pytest tests/`
|
||||
- Run specific test file: `pytest tests/test_gitea_backend.py`
|
||||
- Run with coverage: `pytest tests/ --cov=issue_tracker --cov-report=html --cov-report=term`
|
||||
- Run with coverage: `pytest tests/ --cov=issue_core --cov-report=html --cov-report=term`
|
||||
- Run integration tests: `pytest tests/test_gitea_integration.py -v`
|
||||
|
||||
### Code Quality
|
||||
- Run linter: `make issue-facade-lint`
|
||||
- Format code: `black issue_tracker/ tests/` (line length: 100)
|
||||
- Sort imports: `isort issue_tracker/ tests/`
|
||||
- Run linter: `make issue-core-lint`
|
||||
- Format code: `black issue_core/ tests/` (line length: 100)
|
||||
- Sort imports: `isort issue_core/ tests/`
|
||||
|
||||
### CLI Usage
|
||||
The project provides two entry points: `issue` and `issue-tracker` (both execute `issue_tracker.cli.main:main`)
|
||||
The project provides two entry points: `issue` and `issue-core` (both execute `issue_core.cli.main:main`)
|
||||
|
||||
Common commands:
|
||||
- `issue list` - List issues
|
||||
@@ -44,18 +44,18 @@ The codebase implements a **plugin-based facade pattern** with clear separation
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ CLI Layer (Click) │
|
||||
│ issue_tracker/cli/*.py │
|
||||
│ issue_core/cli/*.py │
|
||||
└───────────────┬─────────────────────────┘
|
||||
│
|
||||
┌───────────────▼─────────────────────────┐
|
||||
│ Core Domain Models │
|
||||
│ issue_tracker/core/models.py │
|
||||
│ issue_core/core/models.py │
|
||||
│ (Issue, Label, User, etc.) │
|
||||
└───────────────┬─────────────────────────┘
|
||||
│
|
||||
┌───────────────▼─────────────────────────┐
|
||||
│ Backend Interface (ABC) │
|
||||
│ issue_tracker/core/interfaces.py │
|
||||
│ issue_core/core/interfaces.py │
|
||||
│ IssueBackend, LocalBackend, │
|
||||
│ RemoteBackend, SyncableBackend │
|
||||
└───────────────┬─────────────────────────┘
|
||||
@@ -70,7 +70,7 @@ The codebase implements a **plugin-based facade pattern** with clear separation
|
||||
|
||||
### Key Components
|
||||
|
||||
#### 1. Core Domain Models (`issue_tracker/core/models.py`)
|
||||
#### 1. Core Domain Models (`issue_core/core/models.py`)
|
||||
- **Issue**: Universal issue model with state management, label categorization, and domain logic
|
||||
- **Label**: Supports categorization (priority/type/status/other) with cached properties
|
||||
- **User, Milestone, Comment**: Supporting models
|
||||
@@ -78,7 +78,7 @@ The codebase implements a **plugin-based facade pattern** with clear separation
|
||||
|
||||
The Issue model uses `@cached_property` for performance optimization and includes domain logic methods (`close()`, `reopen()`, `add_label()`, etc.) that enforce business rules.
|
||||
|
||||
#### 2. Backend Interface (`issue_tracker/core/interfaces.py`)
|
||||
#### 2. Backend Interface (`issue_core/core/interfaces.py`)
|
||||
- **IssueBackend (ABC)**: Defines the contract all backends must implement
|
||||
- **LocalBackend, RemoteBackend**: Marker interfaces for backend categorization
|
||||
- **SyncableBackend**: Interface for backends supporting synchronization
|
||||
@@ -94,19 +94,19 @@ The Issue model uses `@cached_property` for performance optimization and include
|
||||
|
||||
#### 3. Backend Implementations
|
||||
|
||||
**Local Backend** (`issue_tracker/backends/local/backend.py`):
|
||||
**Local Backend** (`issue_core/backends/local/backend.py`):
|
||||
- Uses SQLite with schema defined in `schema.sql`
|
||||
- Full offline functionality
|
||||
- Serves as synchronization source of truth
|
||||
- Implements `LocalBackend` and `SyncableBackend`
|
||||
|
||||
**Gitea Backend** (`issue_tracker/backends/gitea/backend.py`):
|
||||
**Gitea Backend** (`issue_core/backends/gitea/backend.py`):
|
||||
- REST API integration with Gitea instances
|
||||
- Rate limiting and error handling
|
||||
- ID mapping between local and remote issues
|
||||
- Implements `RemoteBackend` and `SyncableBackend`
|
||||
|
||||
#### 4. CLI Layer (`issue_tracker/cli/`)
|
||||
#### 4. CLI Layer (`issue_core/cli/`)
|
||||
- **main.py**: Entry point, Click group setup, command registration
|
||||
- **commands.py**: Core issue operations (list, show, create, close)
|
||||
- **backend_commands.py**: Backend management (add, list, switch)
|
||||
@@ -150,7 +150,7 @@ Run only integration tests: `pytest -m integration`
|
||||
|
||||
### Adding a New Backend
|
||||
|
||||
1. Create backend package in `issue_tracker/backends/<name>/`
|
||||
1. Create backend package in `issue_core/backends/<name>/`
|
||||
2. Implement `IssueBackend` interface (or extend `LocalBackend`/`RemoteBackend`)
|
||||
3. Implement all abstract methods from the interface
|
||||
4. Define `BackendCapabilities` to specify supported features
|
||||
@@ -161,7 +161,7 @@ Run only integration tests: `pytest -m integration`
|
||||
|
||||
### Modifying the Issue Model
|
||||
|
||||
When changing `issue_tracker/core/models.py`:
|
||||
When changing `issue_core/core/models.py`:
|
||||
1. Update the `Issue` dataclass definition
|
||||
2. Update `to_dict()` serialization method
|
||||
3. Invalidate caches if adding/modifying label-dependent properties
|
||||
@@ -181,7 +181,7 @@ When changing `issue_tracker/core/models.py`:
|
||||
## Configuration
|
||||
|
||||
### Project Configuration (`pyproject.toml`)
|
||||
- Entry points: `issue` and `issue-tracker` commands
|
||||
- Entry points: `issue` and `issue-core` commands
|
||||
- Dependencies: click, requests, python-dateutil
|
||||
- Optional dependencies: dev, docs, gitea, github, jira
|
||||
- Code style: Black (line-length=100), isort (profile="black")
|
||||
@@ -189,7 +189,7 @@ When changing `issue_tracker/core/models.py`:
|
||||
|
||||
### Makefile Integration
|
||||
The capability integrates with the parent markitect project via `Makefile`:
|
||||
- Prefixed targets: `issue-facade-*` for development commands
|
||||
- Prefixed targets: `issue-core-*` for development commands
|
||||
- Unprefixed targets: `issue-*` for user-facing CLI operations
|
||||
- Uses `pip install -e` for editable installation
|
||||
|
||||
@@ -239,7 +239,7 @@ When implementing sync:
|
||||
|
||||
## Repository Context
|
||||
|
||||
This is a capability within the larger markitect project (`/capabilities/issue-facade/`). The capability:
|
||||
This is a capability within the larger markitect project (`/capabilities/issue-core/`). The capability:
|
||||
- Can be installed independently via `pip install -e .`
|
||||
- Integrates with parent project via Makefile targets
|
||||
- Follows markitect capability conventions for structure and naming
|
||||
@@ -268,7 +268,7 @@ feedback/
|
||||
|
||||
### For Users: Submitting Feedback
|
||||
|
||||
Users of issue-facade (master projects integrating it) can submit feedback in multiple ways:
|
||||
Users of issue-core (master projects integrating it) can submit feedback in multiple ways:
|
||||
|
||||
**Option 1: Using feedback CLI**
|
||||
```bash
|
||||
@@ -294,8 +294,8 @@ EOF
|
||||
**Option 3: From master project**
|
||||
```bash
|
||||
cd my-master-project
|
||||
echo "Feedback about issue-facade..." > feedback.md
|
||||
cp feedback.md capabilities/issue-facade/feedback/inbound/$(date +%Y%m%d)-feedback.md
|
||||
echo "Feedback about issue-core..." > feedback.md
|
||||
cp feedback.md capabilities/issue-core/feedback/inbound/$(date +%Y%m%d)-feedback.md
|
||||
```
|
||||
|
||||
### For Maintainers: Processing Feedback
|
||||
|
||||
116
INTENT.md
Normal file
116
INTENT.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# INTENT — issue-core
|
||||
|
||||
## Why it exists
|
||||
|
||||
The Coulomb org needs a **single, observable place where tasks land** — regardless
|
||||
of whether they were created by a human typing a CLI command, by an automation
|
||||
like activity-core acting on a rule, or by an agent acting on instructions.
|
||||
|
||||
Without a single landing zone, task creation fragments across:
|
||||
- Per-repo Gitea issue trackers (siloed, no cross-repo view)
|
||||
- Ad hoc files and TODO comments (invisible, unaudited)
|
||||
- Agent-local memory and notebooks (lost when the agent ends)
|
||||
- External SaaS trackers (rate-limited, off-network)
|
||||
|
||||
issue-core gives every actor — human or machine — one stable, observable place
|
||||
to file work, and one stable surface to consume work from.
|
||||
|
||||
## What it is
|
||||
|
||||
A **task lifecycle manager** with a pluggable-backend architecture.
|
||||
|
||||
Responsibilities:
|
||||
- **Ingestion**: accept new tasks via CLI, REST (`POST /issues/`), and — in the
|
||||
future — NATS subscriptions.
|
||||
- **Storage**: route each task to the configured backend (Gitea, SQLite, GitHub).
|
||||
- **Lifecycle**: create → assign → update → close, with state transitions that
|
||||
hold regardless of backend.
|
||||
- **Querying**: list, search, filter across the active backend.
|
||||
- **Synchronization**: bidirectional sync between local SQLite (source of truth
|
||||
for offline work) and remote backends.
|
||||
|
||||
Backends today: **local SQLite**, **Gitea**. Planned: **GitHub**, **GitLab**, **JIRA**.
|
||||
|
||||
The CLI entry points are `issue` (primary) and `issue-core` (explicit alias).
|
||||
|
||||
## What it is NOT
|
||||
|
||||
issue-core is intentionally narrow. The following live elsewhere:
|
||||
|
||||
- **Not a project manager.** Phases, campaigns, milestones spanning multiple tasks,
|
||||
dependency graphs across tasks, gantt-style scheduling — that is the domain of
|
||||
`project-core` (planned). issue-core deals in individual tasks, not in plans
|
||||
composed of tasks.
|
||||
|
||||
- **Not a spawn audit trail.** When activity-core fires a rule that creates a task,
|
||||
the *spawn event* (who fired, what rule, what triggering event) is recorded in
|
||||
activity-core's `task_spawn_log`. issue-core only stores the resulting task and
|
||||
its `triggering_event_id` reference back. The audit-of-creation belongs to the
|
||||
emitter.
|
||||
|
||||
- **Not an event bus.** Communication between services flows over NATS (and
|
||||
state-hub progress events). issue-core consumes events, but does not relay them.
|
||||
|
||||
- **Not a notification system.** Surfacing "your task changed" to humans is the
|
||||
job of the relevant UI / digest / chatbot layer, not issue-core.
|
||||
|
||||
- **Not a workflow engine.** State transitions are simple (open → closed, with
|
||||
a few in-between states). Conditional routing, approvals, multi-step
|
||||
workflows — out of scope.
|
||||
|
||||
## How it fits
|
||||
|
||||
```
|
||||
+-------------------+
|
||||
| activity-core |
|
||||
| IssueSink (REST) |
|
||||
+---------+---------+
|
||||
|
|
||||
POST /issues/ (TaskSpec payload)
|
||||
|
|
||||
v
|
||||
+------------+ +-------+--------+ +-----------------+
|
||||
| Humans +----->| |<-----+ Agents |
|
||||
| CLI: | | issue-core | | (CLI or REST) |
|
||||
| $ issue | | | | |
|
||||
+------------+ +-------+--------+ +-----------------+
|
||||
|
|
||||
+---------+----------+
|
||||
| Backend router |
|
||||
+---+------+------+--+
|
||||
| | |
|
||||
v v v
|
||||
+------+ +-----+ +------+
|
||||
|Gitea | |SQLite| |GitHub|
|
||||
+------+ +-----+ +------+
|
||||
```
|
||||
|
||||
**Upstream of issue-core (emitters):**
|
||||
- **activity-core** — emits tasks via `IssueSink` when a rule fires or an
|
||||
instruction declares one. Payload: `TaskSpec` over `POST /issues/`.
|
||||
- **Humans** — `$ issue create ...` from terminals; future web UI.
|
||||
- **Agents** — same REST surface or CLI.
|
||||
|
||||
**Downstream of issue-core (consumers):**
|
||||
- **Humans and agents** assigned tasks consume them via `$ issue list`, web UI,
|
||||
or per-backend native UIs (Gitea web, GitHub PR view, etc.).
|
||||
- **state-hub** receives progress events as tasks move through their lifecycle.
|
||||
- **Status updates** flow back to issue-core, not to the original emitter —
|
||||
activity-core does not track what happened to the task it spawned.
|
||||
|
||||
## Success looks like
|
||||
|
||||
- Every task in the Coulomb org is discoverable from one query surface.
|
||||
- activity-core can fire a rule and have the resulting task land in the right
|
||||
backend with the right metadata, with no human in the loop.
|
||||
- The CLI experience is identical across SQLite-only laptops and full Gitea-
|
||||
backed servers.
|
||||
- Offline work syncs back cleanly when connectivity returns.
|
||||
|
||||
## See also
|
||||
|
||||
- `SCOPE.md` — concrete in/out-of-scope decisions and integration boundaries.
|
||||
- `ROADMAP.md` — feature trajectory.
|
||||
- `workplans/` — active workstreams.
|
||||
- activity-core `docs/adr/adr-001-event-bridge-architecture.md` — the IssueSink
|
||||
contract that issue-core honors at `POST /issues/`.
|
||||
92
Makefile
92
Makefile
@@ -1,14 +1,14 @@
|
||||
# Issue Facade Capability Makefile
|
||||
# Issue Core Capability Makefile
|
||||
# Universal CLI for issue tracking across multiple backends
|
||||
|
||||
# Capability metadata
|
||||
CAPABILITY_NAME := issue-facade
|
||||
CAPABILITY_NAME := issue-core
|
||||
CAPABILITY_DESCRIPTION := Universal CLI for issue tracking across multiple backends
|
||||
|
||||
# Default target
|
||||
.PHONY: help
|
||||
help: ## Show issue facade capability help
|
||||
@echo "🎯 Issue Facade - Universal Issue Tracking CLI"
|
||||
help: ## Show issue core capability help
|
||||
@echo "🎯 Issue Core - Universal Issue Tracking CLI"
|
||||
@echo "==============================================="
|
||||
@echo ""
|
||||
@echo "Core Issue Operations:"
|
||||
@@ -30,7 +30,7 @@ help: ## Show issue facade capability help
|
||||
@echo " issue-sync-push Push local issues to remote"
|
||||
@echo ""
|
||||
@echo "Development & Setup (local):"
|
||||
@echo " install Install issue facade for local development"
|
||||
@echo " install Install issue core for local development"
|
||||
@echo " install-dev Install with development dependencies"
|
||||
@echo " test Run all tests"
|
||||
@echo " test-unit Run unit tests only"
|
||||
@@ -39,23 +39,23 @@ help: ## Show issue facade capability help
|
||||
@echo " test-verbose Run tests with verbose output"
|
||||
@echo ""
|
||||
@echo "Feedback & Continuous Improvement:"
|
||||
@echo " feedback MSG=\"...\" Submit feedback about issue-facade"
|
||||
@echo " feedback MSG=\"...\" Submit feedback about issue-core"
|
||||
@echo " feedback-list List pending feedback"
|
||||
@echo " feedback-stats Show feedback statistics"
|
||||
@echo " feedback-show FILE=\"...\" Show specific feedback"
|
||||
@echo " feedback-review FILE=\"...\" Review feedback (maintainers)"
|
||||
@echo ""
|
||||
@echo "Development & Setup (from parent):"
|
||||
@echo " issue-facade-install Install issue facade capability"
|
||||
@echo " issue-facade-install-dev Install with development dependencies"
|
||||
@echo " issue-facade-test Run issue facade tests"
|
||||
@echo " issue-facade-test-cov Run tests with coverage report"
|
||||
@echo " issue-facade-lint Run code quality checks"
|
||||
@echo " issue-facade-clean Clean build artifacts"
|
||||
@echo " issue-core-install Install issue core capability"
|
||||
@echo " issue-core-install-dev Install with development dependencies"
|
||||
@echo " issue-core-test Run issue core tests"
|
||||
@echo " issue-core-test-cov Run tests with coverage report"
|
||||
@echo " issue-core-lint Run code quality checks"
|
||||
@echo " issue-core-clean Clean build artifacts"
|
||||
@echo ""
|
||||
@echo "CLI Functionality:"
|
||||
@echo " issue-facade-help Show CLI help documentation"
|
||||
@echo " issue-facade-demo Demonstrate facade functionality"
|
||||
@echo " issue-core-help Show CLI help documentation"
|
||||
@echo " issue-core-demo Demonstrate facade functionality"
|
||||
|
||||
# Check if issue command is available
|
||||
ISSUE_CLI := $(shell command -v issue 2> /dev/null)
|
||||
@@ -65,7 +65,7 @@ ISSUE_CLI := $(shell command -v issue 2> /dev/null)
|
||||
issue-list: ## List all issues from configured backend
|
||||
ifndef ISSUE_CLI
|
||||
@echo "❌ Issue facade not installed"
|
||||
@echo " Install with: make issue-facade-install"
|
||||
@echo " Install with: make issue-core-install"
|
||||
@exit 1
|
||||
endif
|
||||
issue list
|
||||
@@ -182,7 +182,7 @@ integrate: ## Integrate capability into main project (interactive)
|
||||
@./.capability/integrate.sh
|
||||
|
||||
.PHONY: install
|
||||
install: ## Install issue facade (local development)
|
||||
install: ## Install issue core (local development)
|
||||
pip install -e .
|
||||
|
||||
.PHONY: install-dev
|
||||
@@ -203,7 +203,7 @@ test-integration: ## Run integration tests only (local development)
|
||||
|
||||
.PHONY: test-cov
|
||||
test-cov: ## Run tests with coverage report (local development)
|
||||
pytest tests/ --cov=issue_tracker --cov-report=html --cov-report=term
|
||||
pytest tests/ --cov=issue_core --cov-report=html --cov-report=term
|
||||
|
||||
.PHONY: test-verbose
|
||||
test-verbose: ## Run tests with verbose output (local development)
|
||||
@@ -234,50 +234,50 @@ feedback-review: ## Review feedback (Usage: make feedback-review FILE=20251217-x
|
||||
feedback-review-issue: ## Review feedback and create issue (Usage: make feedback-review-issue FILE=20251217-xxx.md)
|
||||
@./.capability/feedback review "$(FILE)" --create-issue
|
||||
|
||||
.PHONY: issue-facade-install
|
||||
issue-facade-install: ## Install issue facade capability
|
||||
pip install -e capabilities/issue-facade/
|
||||
.PHONY: issue-core-install
|
||||
issue-core-install: ## Install issue core capability
|
||||
pip install -e capabilities/issue-core/
|
||||
|
||||
.PHONY: issue-facade-install-dev
|
||||
issue-facade-install-dev: ## Install issue facade capability with development dependencies
|
||||
pip install -e "capabilities/issue-facade/[dev]"
|
||||
.PHONY: issue-core-install-dev
|
||||
issue-core-install-dev: ## Install issue core capability with development dependencies
|
||||
pip install -e "capabilities/issue-core/[dev]"
|
||||
|
||||
.PHONY: issue-facade-test
|
||||
issue-facade-test: ## Run issue facade tests
|
||||
cd capabilities/issue-facade && pytest tests/
|
||||
.PHONY: issue-core-test
|
||||
issue-core-test: ## Run issue core tests
|
||||
cd capabilities/issue-core && pytest tests/
|
||||
|
||||
.PHONY: issue-facade-test-cov
|
||||
issue-facade-test-cov: ## Run tests with coverage report
|
||||
cd capabilities/issue-facade && pytest tests/ --cov=issue_tracker --cov-report=html --cov-report=term
|
||||
.PHONY: issue-core-test-cov
|
||||
issue-core-test-cov: ## Run tests with coverage report
|
||||
cd capabilities/issue-core && pytest tests/ --cov=issue_core --cov-report=html --cov-report=term
|
||||
|
||||
.PHONY: issue-facade-lint
|
||||
issue-facade-lint: ## Run code quality checks
|
||||
@echo "🔍 Running code quality checks for issue-facade..."
|
||||
cd capabilities/issue-facade && python -m py_compile cli/*.py core/*.py backends/*/*.py 2>/dev/null || echo "⚠️ Some files may not compile due to missing dependencies"
|
||||
.PHONY: issue-core-lint
|
||||
issue-core-lint: ## Run code quality checks
|
||||
@echo "🔍 Running code quality checks for issue-core..."
|
||||
cd capabilities/issue-core && python -m py_compile cli/*.py core/*.py backends/*/*.py 2>/dev/null || echo "⚠️ Some files may not compile due to missing dependencies"
|
||||
@echo "✅ Code quality checks completed"
|
||||
|
||||
.PHONY: issue-facade-clean
|
||||
issue-facade-clean: ## Clean build artifacts
|
||||
cd capabilities/issue-facade && rm -rf build/ dist/ *.egg-info/ __pycache__/ .pytest_cache/ htmlcov/ .coverage
|
||||
find capabilities/issue-facade -name "*.pyc" -delete
|
||||
find capabilities/issue-facade -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
.PHONY: issue-core-clean
|
||||
issue-core-clean: ## Clean build artifacts
|
||||
cd capabilities/issue-core && rm -rf build/ dist/ *.egg-info/ __pycache__/ .pytest_cache/ htmlcov/ .coverage
|
||||
find capabilities/issue-core -name "*.pyc" -delete
|
||||
find capabilities/issue-core -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
# CLI Functionality
|
||||
.PHONY: issue-facade-help
|
||||
issue-facade-help: ## Show CLI help documentation
|
||||
.PHONY: issue-core-help
|
||||
issue-core-help: ## Show CLI help documentation
|
||||
ifndef ISSUE_CLI
|
||||
@echo "❌ Issue facade not installed"
|
||||
@echo " Install with: make issue-facade-install"
|
||||
@echo " Install with: make issue-core-install"
|
||||
@exit 1
|
||||
endif
|
||||
issue --help
|
||||
|
||||
.PHONY: issue-facade-demo
|
||||
issue-facade-demo: ## Demonstrate facade functionality
|
||||
@echo "🎬 Issue Facade Demonstration"
|
||||
.PHONY: issue-core-demo
|
||||
issue-core-demo: ## Demonstrate facade functionality
|
||||
@echo "🎬 Issue Core Demonstration"
|
||||
@echo "============================="
|
||||
@echo ""
|
||||
@echo "The Issue Facade provides a unified CLI for issue tracking across:"
|
||||
@echo "The Issue Core provides a unified CLI for issue tracking across:"
|
||||
@echo " • GitHub Issues"
|
||||
@echo " • GitLab Issues"
|
||||
@echo " • Gitea Issues"
|
||||
@@ -290,7 +290,7 @@ issue-facade-demo: ## Demonstrate facade functionality
|
||||
@echo ""
|
||||
ifndef ISSUE_CLI
|
||||
@echo "To try it out:"
|
||||
@echo " 1. make issue-facade-install"
|
||||
@echo " 1. make issue-core-install"
|
||||
@echo " 2. make issue-backend-detect"
|
||||
@echo " 3. make issue-list"
|
||||
else
|
||||
|
||||
43
README.md
43
README.md
@@ -1,10 +1,10 @@
|
||||
# Issue Facade - Agent Coordination via Issue Tracking
|
||||
# Issue Core - Agent Coordination via Issue Tracking
|
||||
|
||||
**A unified interface for autonomous coding agents to coordinate project implementation through issue tracking systems.**
|
||||
|
||||
## Purpose
|
||||
|
||||
The **Issue Facade** provides a standardized abstraction layer for coding agents to interact with issue tracking backends (Gitea, GitHub, GitLab, local SQLite). Instead of each agent implementing platform-specific API integrations, they use one consistent interface that works across all backends.
|
||||
The **Issue Core** provides a standardized abstraction layer for coding agents to interact with issue tracking backends (Gitea, GitHub, GitLab, local SQLite). Instead of each agent implementing platform-specific API integrations, they use one consistent interface that works across all backends.
|
||||
|
||||
### Why Issue Tracking for Agent Coordination?
|
||||
|
||||
@@ -40,10 +40,25 @@ Issue tracking provides natural coordination primitives for multi-agent software
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
cd capabilities/issue-facade
|
||||
pip install -e .
|
||||
cd capabilities/issue-core
|
||||
pip install -e . # CLI only
|
||||
pip install -e ".[api]" # CLI + REST ingestion server
|
||||
```
|
||||
|
||||
### REST Ingestion Server
|
||||
|
||||
issue-core exposes `POST /issues/` for upstream emitters (primarily
|
||||
activity-core's `IssueSink`). Launch with:
|
||||
|
||||
```bash
|
||||
export ISSUE_CORE_API_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
|
||||
issue serve --host 0.0.0.0 --port 8765
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Configuration (One-Time Setup)
|
||||
|
||||
**For Gitea-backed projects:**
|
||||
@@ -67,7 +82,7 @@ issue backend test myproject
|
||||
|
||||
```bash
|
||||
issue backend add local-work local
|
||||
# Prompts for: database path (.issue-facade/issues.db)
|
||||
# Prompts for: database path (.issue-core/issues.db)
|
||||
|
||||
issue backend set-default local-work
|
||||
```
|
||||
@@ -108,8 +123,8 @@ issue close 42 --comment="Ready for review"
|
||||
Quick example:
|
||||
|
||||
```python
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
# Initialize
|
||||
backend = GiteaBackend()
|
||||
@@ -281,20 +296,20 @@ make test-unit
|
||||
|
||||
```bash
|
||||
# Run linter
|
||||
make issue-facade-lint
|
||||
make issue-core-lint
|
||||
|
||||
# Format code
|
||||
black issue_tracker/ tests/
|
||||
black issue_core/ tests/
|
||||
|
||||
# Type check
|
||||
mypy issue_tracker/
|
||||
mypy issue_core/
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
issue-facade/
|
||||
├── issue_tracker/
|
||||
issue-core/
|
||||
├── issue_core/
|
||||
│ ├── core/ # Domain models and interfaces
|
||||
│ │ ├── models.py # Issue, Label, User, etc.
|
||||
│ │ └── interfaces.py # IssueBackend, SyncableBackend
|
||||
@@ -351,7 +366,7 @@ issue-facade/
|
||||
|
||||
## Comparison with Platform CLIs
|
||||
|
||||
| Feature | Issue Facade | gh (GitHub) | glab (GitLab) |
|
||||
| Feature | Issue Core | gh (GitHub) | glab (GitLab) |
|
||||
|---------|--------------|-------------|---------------|
|
||||
| Multi-backend support | ✅ Yes | ❌ GitHub only | ❌ GitLab only |
|
||||
| Offline capability | ✅ Local SQLite | ❌ No | ❌ No |
|
||||
@@ -362,7 +377,7 @@ issue-facade/
|
||||
|
||||
## Contributing
|
||||
|
||||
The Issue Facade is designed to be extensible:
|
||||
The Issue Core is designed to be extensible:
|
||||
|
||||
**To add a new backend:**
|
||||
1. Implement the `IssueBackend` interface (see `core/interfaces.py`)
|
||||
|
||||
36
ROADMAP.md
36
ROADMAP.md
@@ -1,4 +1,4 @@
|
||||
# Issue Facade Roadmap
|
||||
# Issue Core Roadmap
|
||||
|
||||
**Long-term vision and implementation plan for agent-driven software development coordination.**
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/detection.py
|
||||
# issue_core/core/detection.py
|
||||
|
||||
def detect_git_remote() -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
@@ -64,7 +64,7 @@ def parse_remote_url(url: str) -> Optional[Dict[str, str]]:
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/env_config.py
|
||||
# issue_core/core/env_config.py
|
||||
|
||||
def load_backend_from_env() -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -95,7 +95,7 @@ issue config auto
|
||||
|
||||
**Implementation:**
|
||||
```
|
||||
.issue-facade/
|
||||
.issue-core/
|
||||
├── config.json # Repository-specific settings
|
||||
├── issues.db # Local cache/backup
|
||||
└── credentials.json # Optional encrypted credentials
|
||||
@@ -126,10 +126,10 @@ issue config auto
|
||||
**Functions:**
|
||||
```python
|
||||
def load_repo_config(path: Path = Path.cwd()) -> Optional[Dict]:
|
||||
"""Load .issue-facade/config.json from repo root."""
|
||||
"""Load .issue-core/config.json from repo root."""
|
||||
|
||||
def save_repo_config(config: Dict, path: Path = Path.cwd()):
|
||||
"""Save config to .issue-facade/config.json."""
|
||||
"""Save config to .issue-core/config.json."""
|
||||
|
||||
def find_repo_root() -> Optional[Path]:
|
||||
"""Walk up directory tree to find git root."""
|
||||
@@ -141,14 +141,14 @@ def find_repo_root() -> Optional[Path]:
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/auto_config.py
|
||||
# issue_core/core/auto_config.py
|
||||
|
||||
def auto_configure_backend() -> IssueBackend:
|
||||
"""
|
||||
Auto-configure backend with fallback priority:
|
||||
1. Check .issue-facade/config.json
|
||||
1. Check .issue-core/config.json
|
||||
2. Detect from git remote + environment token
|
||||
3. Check global config (~/.config/issue-facade/)
|
||||
3. Check global config (~/.config/issue-core/)
|
||||
4. Prompt user for manual configuration
|
||||
"""
|
||||
```
|
||||
@@ -196,7 +196,7 @@ if issue backend show "$backend_name" &>/dev/null; then
|
||||
if [ "$replace" = "y" ] || [ "$replace" = "Y" ]; then
|
||||
# Create timestamped backup
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
CONFIG_FILE="$HOME/.config/issue-facade/backends.json"
|
||||
CONFIG_FILE="$HOME/.config/issue-core/backends.json"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
BACKUP_FILE="$CONFIG_FILE.backup.$TIMESTAMP"
|
||||
cp "$CONFIG_FILE" "$BACKUP_FILE"
|
||||
@@ -255,7 +255,7 @@ fi
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/agent.py
|
||||
# issue_core/core/agent.py
|
||||
|
||||
@dataclass
|
||||
class AgentContext:
|
||||
@@ -269,7 +269,7 @@ def get_agent_context() -> AgentContext:
|
||||
"""
|
||||
Get agent context from:
|
||||
1. Environment (ISSUE_AGENT_ID, ISSUE_AGENT_TYPE)
|
||||
2. Config file (.issue-facade/config.json)
|
||||
2. Config file (.issue-core/config.json)
|
||||
3. Default to system username
|
||||
"""
|
||||
|
||||
@@ -310,7 +310,7 @@ issue config agent show
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/locking.py
|
||||
# issue_core/core/locking.py
|
||||
|
||||
class IssueClaim:
|
||||
issue_id: str
|
||||
@@ -423,7 +423,7 @@ def get_agent_state(issue: Issue) -> Dict[str, Any]:
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/webhooks.py
|
||||
# issue_core/core/webhooks.py
|
||||
|
||||
class WebhookManager:
|
||||
"""Manage webhooks for real-time notifications."""
|
||||
@@ -525,7 +525,7 @@ issue depends ready # List issues ready to start
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/query_dsl.py
|
||||
# issue_core/core/query_dsl.py
|
||||
|
||||
class QueryParser:
|
||||
"""
|
||||
@@ -559,7 +559,7 @@ issue list --query="is:in-progress created:>7d"
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/activity.py
|
||||
# issue_core/core/activity.py
|
||||
|
||||
@dataclass
|
||||
class ActivityEvent:
|
||||
@@ -596,7 +596,7 @@ class ActivityStream:
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/distributed_lock.py
|
||||
# issue_core/core/distributed_lock.py
|
||||
|
||||
class DistributedLockManager:
|
||||
"""Distributed locking using Redis/database."""
|
||||
@@ -638,7 +638,7 @@ with distributed_lock(f"issue:{issue_id}", agent_id):
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# issue_tracker/core/sync_strategies.py
|
||||
# issue_core/core/sync_strategies.py
|
||||
|
||||
class ConflictResolutionStrategy(ABC):
|
||||
def resolve(self, local: Issue, remote: Issue) -> Issue:
|
||||
|
||||
@@ -50,7 +50,7 @@ A **Capability Family** is an abstract, conceptual grouping of related functiona
|
||||
A **Capability Implementation** is a concrete realization of a Capability Family - the actual code, tools, and patterns that provide the functionality.
|
||||
|
||||
**Examples:**
|
||||
- `issue-facade` - An implementation of the `issue-tracking` family
|
||||
- `issue-core` - An implementation of the `issue-tracking` family
|
||||
- `feedback-tool` - An implementation of the `feedback-collection` family
|
||||
|
||||
**Characteristics:**
|
||||
@@ -94,7 +94,7 @@ CAPABILITY-authentication.yaml # Declares authentication capability
|
||||
### Single Capability Repository Structure
|
||||
|
||||
```
|
||||
issue-facade/ # Repository name (implementation)
|
||||
issue-core/ # Repository name (implementation)
|
||||
├── CAPABILITY-issue-tracking.yaml # Declares: provides "issue-tracking"
|
||||
├── README.md # Human-readable documentation
|
||||
├── feedback/ # Visible: user interface for feedback
|
||||
@@ -105,7 +105,7 @@ issue-facade/ # Repository name (implementation)
|
||||
│ ├── feedback # Feedback CLI tool
|
||||
│ ├── integrate.sh # Integration script
|
||||
│ └── ...
|
||||
├── issue_tracker/ # Core implementation code
|
||||
├── issue_core/ # Core implementation code
|
||||
│ ├── core/
|
||||
│ ├── backends/
|
||||
│ └── cli/
|
||||
@@ -141,7 +141,7 @@ unified-devtools/ # Repository providing multiple capab
|
||||
│ ├── feedback-collection/
|
||||
│ └── common/
|
||||
├── src/
|
||||
│ ├── issue_tracker/
|
||||
│ ├── issue_core/
|
||||
│ ├── feedback_tool/
|
||||
│ └── doc_generator/
|
||||
└── tests/
|
||||
@@ -162,8 +162,8 @@ Traditional approaches create deep directory trees:
|
||||
```
|
||||
my-project/
|
||||
└── capabilities/
|
||||
└── issue-facade/
|
||||
└── issue_tracker/
|
||||
└── issue-core/
|
||||
└── issue_core/
|
||||
└── backends/
|
||||
└── gitea/
|
||||
└── backend.py # 6 levels deep!
|
||||
@@ -176,8 +176,8 @@ Use **underscore-prefixed directories** at the repository root to signal "integr
|
||||
**Option A: Implementation-Based (Recommended)**
|
||||
```
|
||||
my-project/
|
||||
├── _issue-facade/ # Integrated capability (flat!)
|
||||
│ └── issue_tracker/ # Only 2-3 levels deep
|
||||
├── _issue-core/ # Integrated capability (flat!)
|
||||
│ └── issue_core/ # Only 2-3 levels deep
|
||||
│ └── backends/
|
||||
├── _feedback-tool/ # Another integrated capability
|
||||
├── src/ # Core project code
|
||||
@@ -190,7 +190,7 @@ my-project/
|
||||
```
|
||||
my-project/
|
||||
├── _issue-tracking/ # Capability family
|
||||
│ └── issue-facade/ # Implementation (if multiple needed)
|
||||
│ └── issue-core/ # Implementation (if multiple needed)
|
||||
├── _feedback-collection/
|
||||
│ └── feedback-tool/
|
||||
└── src/
|
||||
@@ -200,7 +200,7 @@ my-project/
|
||||
```
|
||||
my-project/
|
||||
├── c/ # "c" for capabilities
|
||||
│ ├── issue-facade/
|
||||
│ ├── issue-core/
|
||||
│ └── feedback-tool/
|
||||
└── src/
|
||||
```
|
||||
@@ -208,7 +208,7 @@ my-project/
|
||||
**Recommendation: Option A (Implementation-Based)**
|
||||
|
||||
**Rationale:**
|
||||
- **Flatter hierarchy** - `_issue-facade/issue_tracker/...` (3 levels vs 6)
|
||||
- **Flatter hierarchy** - `_issue-core/issue_core/...` (3 levels vs 6)
|
||||
- **Clear signal** - underscore means "integrated, not core"
|
||||
- **Discoverable** - `ls _*/` shows all integrated capabilities
|
||||
- **No ambiguity** - implementation name is explicit
|
||||
@@ -217,7 +217,7 @@ my-project/
|
||||
**Example:**
|
||||
```
|
||||
my-project/
|
||||
├── _issue-facade/ # Issue tracking via issue-facade
|
||||
├── _issue-core/ # Issue tracking via issue-core
|
||||
├── _auth-service/ # Authentication via auth-service
|
||||
├── _postgres-tools/ # Database tools
|
||||
├── src/ # Core project code
|
||||
@@ -236,11 +236,11 @@ my-project/
|
||||
ls -d _*/
|
||||
|
||||
# Read capability specs
|
||||
cat _issue-facade/CAPABILITY-*.yaml
|
||||
cat _issue-core/CAPABILITY-*.yaml
|
||||
cat _auth-service/CAPABILITY-*.yaml
|
||||
```
|
||||
|
||||
**Important:** The underscore prefix is **local convention** for integrated capabilities, not part of the upstream git repository name. When integrating `github.com/markitect/issue-facade`, it becomes `_issue-facade/` in your project.
|
||||
**Important:** The underscore prefix is **local convention** for integrated capabilities, not part of the upstream git repository name. When integrating `github.com/markitect/issue-core`, it becomes `_issue-core/` in your project.
|
||||
|
||||
---
|
||||
|
||||
@@ -252,7 +252,7 @@ cat _auth-service/CAPABILITY-*.yaml
|
||||
# CAPABILITY-issue-tracking.yaml
|
||||
metadata:
|
||||
family: issue-tracking
|
||||
implementation: issue-facade
|
||||
implementation: issue-core
|
||||
version: 1.0.0
|
||||
description: >
|
||||
Unified interface for issue tracking across Gitea, GitHub, GitLab.
|
||||
@@ -276,7 +276,7 @@ integration:
|
||||
mcp_server: false # planned
|
||||
|
||||
installation:
|
||||
command: "pip install -e _issue-facade/"
|
||||
command: "pip install -e _issue-core/"
|
||||
verify: "issue --version"
|
||||
|
||||
documentation:
|
||||
@@ -295,7 +295,7 @@ feedback:
|
||||
# CAPABILITY-issue-tracking.yaml
|
||||
metadata:
|
||||
family: issue-tracking
|
||||
implementation: issue-facade
|
||||
implementation: issue-core
|
||||
version: 1.0.0
|
||||
maturity: production # experimental, beta, production
|
||||
|
||||
@@ -338,7 +338,7 @@ integration:
|
||||
methods:
|
||||
python_api:
|
||||
available: true
|
||||
import: "from issue_tracker.backends.gitea import GiteaBackend"
|
||||
import: "from issue_core.backends.gitea import GiteaBackend"
|
||||
docs: "AGENT_INTEGRATION.md"
|
||||
|
||||
cli:
|
||||
@@ -354,7 +354,7 @@ integration:
|
||||
|
||||
installation:
|
||||
method: pip
|
||||
command: "pip install -e _issue-facade/"
|
||||
command: "pip install -e _issue-core/"
|
||||
verify: "issue --version"
|
||||
|
||||
configuration:
|
||||
@@ -421,8 +421,8 @@ credentials:
|
||||
|
||||
security:
|
||||
- "Tokens never in code or logs"
|
||||
- "Config stored in ~/.config/issue-facade/"
|
||||
- "Per-repo config in .issue-facade/ (gitignored)"
|
||||
- "Config stored in ~/.config/issue-core/"
|
||||
- "Per-repo config in .issue-core/ (gitignored)"
|
||||
|
||||
# Documentation references
|
||||
documentation:
|
||||
@@ -504,13 +504,13 @@ cat feedback/README.md
|
||||
**Integrate a capability:**
|
||||
```bash
|
||||
# Clone/copy into underscore-prefixed directory
|
||||
git clone https://github.com/markitect/issue-facade _issue-facade
|
||||
git clone https://github.com/markitect/issue-core _issue-core
|
||||
|
||||
# Or use git submodule
|
||||
git submodule add https://github.com/markitect/issue-facade _issue-facade
|
||||
git submodule add https://github.com/markitect/issue-core _issue-core
|
||||
|
||||
# Install
|
||||
pip install -e _issue-facade/
|
||||
pip install -e _issue-core/
|
||||
|
||||
# Verify
|
||||
issue --version
|
||||
@@ -546,18 +546,18 @@ def find_capability_family(repo_path, family_name):
|
||||
return None
|
||||
|
||||
# Usage
|
||||
capabilities = discover_capabilities("_issue-facade")
|
||||
capabilities = discover_capabilities("_issue-core")
|
||||
# Returns: [{'metadata': {'family': 'issue-tracking', ...}}]
|
||||
|
||||
issue_cap = find_capability_family("_issue-facade", "issue-tracking")
|
||||
issue_cap = find_capability_family("_issue-core", "issue-tracking")
|
||||
print(f"Found: {issue_cap['metadata']['implementation']}")
|
||||
# Output: Found: issue-facade
|
||||
# Output: Found: issue-core
|
||||
```
|
||||
|
||||
**Use capability via natural language understanding:**
|
||||
```python
|
||||
# Agent reads capability spec
|
||||
spec = find_capability_family("_issue-facade", "issue-tracking")
|
||||
spec = find_capability_family("_issue-core", "issue-tracking")
|
||||
|
||||
# Agent understands use-cases
|
||||
use_cases = spec['purpose']['use_cases']
|
||||
@@ -566,7 +566,7 @@ use_cases = spec['purpose']['use_cases']
|
||||
# Agent discovers integration methods
|
||||
if spec['integration']['methods']['python_api']['available']:
|
||||
import_statement = spec['integration']['methods']['python_api']['import']
|
||||
# "from issue_tracker.backends.gitea import GiteaBackend"
|
||||
# "from issue_core.backends.gitea import GiteaBackend"
|
||||
|
||||
# Agent can now integrate programmatically
|
||||
exec(import_statement)
|
||||
@@ -616,7 +616,7 @@ metadata:
|
||||
# This capability integrates other capabilities
|
||||
integrates:
|
||||
- family: issue-tracking
|
||||
implementation: issue-facade
|
||||
implementation: issue-core
|
||||
version: ">=1.0.0"
|
||||
reason: "Uses issue tracking for task management"
|
||||
|
||||
@@ -635,12 +635,12 @@ integrates:
|
||||
```
|
||||
pm-tool/
|
||||
├── CAPABILITY-project-management.yaml
|
||||
├── _issue-facade/ # Integrated capability
|
||||
├── _issue-core/ # Integrated capability
|
||||
├── _feedback-tool/ # Integrated capability
|
||||
├── _doc-gen/ # Integrated capability
|
||||
├── src/
|
||||
│ └── pm/
|
||||
│ ├── tasks.py # Uses issue-facade
|
||||
│ ├── tasks.py # Uses issue-core
|
||||
│ ├── feedback.py # Uses feedback-tool
|
||||
│ └── docs.py # Uses doc-gen
|
||||
└── README.md
|
||||
@@ -660,7 +660,7 @@ integrates:
|
||||
**Multiple valid integrations:**
|
||||
```
|
||||
pm-tool/
|
||||
├── _issue-facade/ # Option 1: issue-facade implementation
|
||||
├── _issue-core/ # Option 1: issue-core implementation
|
||||
└── ...
|
||||
|
||||
pm-tool/
|
||||
@@ -740,7 +740,7 @@ Project C needs Gitea issues
|
||||
|
||||
→ Pattern: "We need issue tracking"
|
||||
→ Family: "issue-tracking"
|
||||
→ Implementation 1: issue-facade (multi-backend)
|
||||
→ Implementation 1: issue-core (multi-backend)
|
||||
→ Implementation 2: github-native (GitHub-only, optimized)
|
||||
→ Implementation 3: jira-bridge (JIRA connector)
|
||||
|
||||
@@ -757,7 +757,7 @@ All implement "issue-tracking" family with different trade-offs
|
||||
**Implementation Versions:**
|
||||
- Implementations evolve independently
|
||||
- Follow semantic versioning (semver)
|
||||
- Example: `issue-facade@1.2.3`, `github-native@0.5.0`
|
||||
- Example: `issue-core@1.2.3`, `github-native@0.5.0`
|
||||
|
||||
**Compatibility:**
|
||||
```yaml
|
||||
@@ -765,7 +765,7 @@ All implement "issue-tracking" family with different trade-offs
|
||||
metadata:
|
||||
family: issue-tracking
|
||||
family_version: "1.x" # Compatible with v1 family spec
|
||||
implementation: issue-facade
|
||||
implementation: issue-core
|
||||
version: 1.2.3 # Implementation version
|
||||
```
|
||||
|
||||
@@ -785,9 +785,9 @@ metadata:
|
||||
|
||||
### For Capability Users (Integrators)
|
||||
|
||||
1. **Prefer Families over Implementations** - Depend on `issue-tracking`, not `issue-facade`
|
||||
1. **Prefer Families over Implementations** - Depend on `issue-tracking`, not `issue-core`
|
||||
2. **Pin Versions Loosely** - `>=1.0.0, <2.0.0` allows updates
|
||||
3. **Use Underscore Prefix** - `_issue-facade/` signals "integrated"
|
||||
3. **Use Underscore Prefix** - `_issue-core/` signals "integrated"
|
||||
4. **Provide Feedback** - Use `feedback/` to guide improvement
|
||||
5. **Read the Spec** - `CAPABILITY-*.yaml` is the contract
|
||||
6. **Check Maturity** - Match capability maturity to your needs
|
||||
@@ -846,7 +846,7 @@ git commit -m "refactor: align with ReusableCapabilitiesArchitecture v0.1"
|
||||
**Solution:** Underscore signals "used by this repo, not core to it"
|
||||
|
||||
**Benefits:**
|
||||
- Visual distinction: `_issue-facade/` vs `src/`
|
||||
- Visual distinction: `_issue-core/` vs `src/`
|
||||
- Flatter hierarchy: 2-3 levels vs 5-6 levels
|
||||
- Easy discovery: `ls _*/` lists all integrations
|
||||
- Works with tab completion: `cd _<tab>` shows capabilities
|
||||
@@ -877,8 +877,8 @@ multi-backend-tool/
|
||||
├── CAPABILITY-issue-tracking-v1.yaml # Stable, production backend
|
||||
├── CAPABILITY-issue-tracking-v2.yaml # Experimental, next-gen backend
|
||||
├── src/
|
||||
│ ├── v1/ # issue-facade-classic
|
||||
│ └── v2/ # issue-facade-next
|
||||
│ ├── v1/ # issue-core-classic
|
||||
│ └── v2/ # issue-core-next
|
||||
└── README.md
|
||||
```
|
||||
|
||||
@@ -896,10 +896,10 @@ Agents consider:
|
||||
```python
|
||||
# Agent logic
|
||||
capabilities = discover_capabilities(".")
|
||||
issue_trackers = [c for c in capabilities if c['metadata']['family'] == 'issue-tracking']
|
||||
issue_cores = [c for c in capabilities if c['metadata']['family'] == 'issue-tracking']
|
||||
|
||||
# Filter by maturity
|
||||
production_ready = [c for c in issue_trackers if c['metadata']['maturity'] == 'production']
|
||||
production_ready = [c for c in issue_cores if c['metadata']['maturity'] == 'production']
|
||||
|
||||
# Choose highest version
|
||||
best = max(production_ready, key=lambda c: c['metadata']['version'])
|
||||
@@ -986,13 +986,13 @@ devtools-suite/
|
||||
|
||||
```
|
||||
my-saas-app/
|
||||
├── _issue-facade/ # Issue tracking capability
|
||||
├── _issue-core/ # Issue tracking capability
|
||||
├── _auth-service/ # Authentication capability
|
||||
├── _feedback-tool/ # Feedback collection capability
|
||||
├── src/
|
||||
│ └── myapp/
|
||||
│ ├── api/
|
||||
│ │ ├── issues.py # Uses _issue-facade
|
||||
│ │ ├── issues.py # Uses _issue-core
|
||||
│ │ └── auth.py # Uses _auth-service
|
||||
│ ├── models/
|
||||
│ └── main.py
|
||||
@@ -1004,15 +1004,15 @@ my-saas-app/
|
||||
**Installation:**
|
||||
```bash
|
||||
# Clone integrated capabilities
|
||||
git submodule add https://github.com/markitect/issue-facade _issue-facade
|
||||
git submodule add https://github.com/markitect/issue-core _issue-core
|
||||
git submodule add https://github.com/markitect/auth-service _auth-service
|
||||
git submodule add https://github.com/markitect/feedback-tool _feedback-tool
|
||||
|
||||
# Install
|
||||
pip install -e _issue-facade/ -e _auth-service/ -e _feedback-tool/
|
||||
pip install -e _issue-core/ -e _auth-service/ -e _feedback-tool/
|
||||
|
||||
# Use in code
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from auth_service import AuthManager
|
||||
```
|
||||
|
||||
|
||||
164
SCOPE.md
Normal file
164
SCOPE.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# SCOPE — issue-core
|
||||
|
||||
Concrete in-scope / out-of-scope decisions for issue-core. Paired with `INTENT.md`,
|
||||
which explains *why*; this file states *what* and *what not*.
|
||||
|
||||
## In scope
|
||||
|
||||
### Task CRUD across backends
|
||||
|
||||
- **Create** issues with title, description, labels, priority, type, milestone,
|
||||
assignee, due date.
|
||||
- **Read** individual issues and lists with filters (state, labels, priority,
|
||||
assignee, text search).
|
||||
- **Update** any mutable field on existing issues.
|
||||
- **Close / reopen** with state transitions enforced at the domain layer.
|
||||
- **Delete** where the backend allows (local SQLite; soft-archive elsewhere).
|
||||
- **Comment** threads on issues.
|
||||
|
||||
### Backend abstraction
|
||||
|
||||
- A single `IssueBackend` ABC contract that every backend implements.
|
||||
- `BackendCapabilities` declares which optional features a backend supports
|
||||
(bulk update, search, milestones, etc.).
|
||||
- A `BackendFactory` registry that maps config to backend instances.
|
||||
|
||||
### Backends shipped today
|
||||
|
||||
- **Local SQLite** — offline source of truth.
|
||||
- **Gitea** — REST API integration.
|
||||
|
||||
### Backends planned
|
||||
|
||||
- **GitHub** — Issues + PRs.
|
||||
- **GitLab** — Issues.
|
||||
- **JIRA** — issues with story-points.
|
||||
|
||||
### Ingestion surfaces
|
||||
|
||||
- **CLI** (`issue` / `issue-core`) for humans and agents on a shell.
|
||||
- **REST** (`POST /issues/`) for automation — primarily activity-core's
|
||||
`IssueSink`, but open to any well-authenticated client.
|
||||
- **NATS subscriber** (design stub only — implementation deferred until
|
||||
activity-core migrates from REST to NATS, see `docs/nats-task-ingestion.md`).
|
||||
|
||||
### Synchronization
|
||||
|
||||
- Local SQLite ↔ remote backends, bidirectional.
|
||||
- `get_issues_modified_since()` on `SyncableBackend` for incremental sync.
|
||||
- Conflict resolution via `SyncableBackend.resolve_sync_conflict()`.
|
||||
- Sync metadata (last-synced timestamps, remote IDs) stored on `Issue.sync_metadata`.
|
||||
|
||||
### State Hub integration
|
||||
|
||||
- Registered as a custodian-domain repo.
|
||||
- Emits `add_progress_event()` calls on significant task lifecycle moments.
|
||||
- Surfaces blocked tasks via the hub's `list_blocked_tasks()` view.
|
||||
|
||||
## Out of scope
|
||||
|
||||
### Project management
|
||||
|
||||
Phases, campaigns, milestones-as-plans, dependency graphs between tasks,
|
||||
gantt-style scheduling, OKR linkage. **That is `project-core` (planned).**
|
||||
issue-core operates on individual tasks. A milestone field exists for
|
||||
flat grouping, not for multi-stage plans.
|
||||
|
||||
### Spawn audit trail
|
||||
|
||||
When activity-core's IssueSink files a task here, the *spawn event* (who fired
|
||||
the rule, against which activity definition, on which triggering event) is
|
||||
recorded in **activity-core's `task_spawn_log`**. issue-core stores the resulting
|
||||
task and a back-reference (`triggering_event_id`) — nothing more.
|
||||
|
||||
Symmetrically: issue-core does not push status updates back to activity-core.
|
||||
Lifecycle updates stay here; activity-core does not care what happens to a task
|
||||
it spawned.
|
||||
|
||||
### Event bus / message broker
|
||||
|
||||
Inter-service communication runs on NATS managed elsewhere. issue-core consumes
|
||||
specific subjects (future) and exposes a REST surface; it does not relay events
|
||||
between other services.
|
||||
|
||||
### Notifications
|
||||
|
||||
Telling a human "your task changed" is the job of the relevant UI, digest,
|
||||
chatbot, or notification service — not issue-core. issue-core emits progress
|
||||
events; downstream consumers decide what to do with them.
|
||||
|
||||
### Workflow / approval engine
|
||||
|
||||
State machine is intentionally small (OPEN, CLOSED, IN_PROGRESS, BLOCKED).
|
||||
Conditional routing, approval chains, multi-step workflows, SLA timers — all
|
||||
out of scope.
|
||||
|
||||
### UI
|
||||
|
||||
issue-core is CLI + REST first. Each backend brings its own native UI
|
||||
(Gitea web, GitHub web, etc.) and that is enough. A web UI for issue-core
|
||||
itself is not on the roadmap.
|
||||
|
||||
### Identity / access management
|
||||
|
||||
Authentication relies on backend credentials (Gitea tokens, GitHub tokens) and
|
||||
on a service-level API key for the REST ingestion endpoint. issue-core is not a
|
||||
user directory.
|
||||
|
||||
## Integration boundaries
|
||||
|
||||
### Upstream emitters
|
||||
|
||||
| Emitter | Transport | Payload | Notes |
|
||||
|----------------|----------------------|--------------------------|-------|
|
||||
| Human CLI | local process call | CLI args | The classic path. |
|
||||
| activity-core | REST `POST /issues/` | `TaskSpec` (see below) | Primary integration; planned NATS migration. |
|
||||
| Agents | REST or CLI | `TaskSpec` or CLI args | Same surfaces as humans/automation. |
|
||||
|
||||
### Downstream consumers
|
||||
|
||||
| Consumer | Mechanism | Notes |
|
||||
|----------------------|------------------------------------|-------|
|
||||
| Humans / agents | `issue list`, web UI, backend UI | Standard task pickup. |
|
||||
| state-hub | `add_progress_event()` calls | Lifecycle visibility. |
|
||||
| Backend remote (e.g. Gitea) | direct backend write | Pass-through for the storage layer. |
|
||||
|
||||
### `TaskSpec` payload (from activity-core's IssueSink)
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"target_repo": "string",
|
||||
"priority": "high | medium | low",
|
||||
"labels": ["string"],
|
||||
"due_in_days": 7,
|
||||
"source_type": "rule | instruction",
|
||||
"source_id": "string",
|
||||
"triggering_event_id": "uuid",
|
||||
"activity_definition_id": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /issues/` response
|
||||
|
||||
```json
|
||||
{
|
||||
"issue_id": "string",
|
||||
"issue_url": "string or null",
|
||||
"backend": "gitea | sqlite | github"
|
||||
}
|
||||
```
|
||||
|
||||
The `issue_id` is the canonical back-reference activity-core stores in its
|
||||
`task_spawn_log`. It is owned and managed by issue-core; activity-core does not
|
||||
mutate it.
|
||||
|
||||
## See also
|
||||
|
||||
- `INTENT.md` — why issue-core exists and how it fits in the Coulomb org.
|
||||
- `ROADMAP.md` — feature trajectory.
|
||||
- `workplans/ISSC-WP-0001-rename-and-task-ingestion.md` — current rename +
|
||||
ingestion workstream.
|
||||
- activity-core `docs/adr/adr-001-event-bridge-architecture.md` — the upstream
|
||||
side of the IssueSink contract.
|
||||
143
docs/nats-task-ingestion.md
Normal file
143
docs/nats-task-ingestion.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# NATS Task Ingestion — Design Stub
|
||||
|
||||
**Status:** design stub. Implementation deferred until activity-core's
|
||||
`IssueSink` migrates from REST to NATS.
|
||||
|
||||
**Scope:** describe what the NATS-backed counterpart of `POST /issues/` will
|
||||
look like, so the activity-core agent and any other future emitter can plan
|
||||
against a stable contract.
|
||||
|
||||
## Why NATS
|
||||
|
||||
Today the ingestion surface is `POST /issues/` — synchronous REST with an API
|
||||
key. That works for activity-core's first cut but has limitations:
|
||||
|
||||
- **Coupling**: activity-core needs to know the URL and key of every issue-core
|
||||
instance. With NATS, both sides connect to a shared broker; routing is by
|
||||
subject.
|
||||
- **Backpressure**: REST is best-effort. If issue-core is down or slow, the
|
||||
emitter either blocks or drops. With NATS JetStream, messages are durable and
|
||||
replay-capable.
|
||||
- **Fan-out**: REST has one consumer. NATS supports multiple consumers (e.g. an
|
||||
audit logger sitting alongside the actual ingester) trivially.
|
||||
- **Replay**: incidents that lose tasks can be reconstructed from the JetStream
|
||||
log if the consumer was offline.
|
||||
|
||||
## Subject pattern
|
||||
|
||||
```
|
||||
act.tasks.create.{target_repo}
|
||||
```
|
||||
|
||||
- Namespace prefix `act.tasks.` (the `act` is activity-core's heritage — the
|
||||
subject prefix is now neutral and other emitters can publish on it too).
|
||||
- `create` is the verb. Future verbs (`act.tasks.update`, `act.tasks.close`)
|
||||
are reserved but not in scope here.
|
||||
- `{target_repo}` is the same string field as the REST `TaskSpec.target_repo`.
|
||||
It allows subject-based routing in consumers: an issue-core instance
|
||||
responsible only for one repo subscribes to `act.tasks.create.myrepo`, while
|
||||
a multi-tenant instance subscribes to `act.tasks.create.>`.
|
||||
|
||||
## Message schema
|
||||
|
||||
The payload is the **exact same** schema as the REST endpoint —
|
||||
`TaskIngestionRequest` in `issue_core/api/schemas.py`:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "string",
|
||||
"description": "string",
|
||||
"target_repo": "string",
|
||||
"priority": "high | medium | low",
|
||||
"labels": ["string"],
|
||||
"due_in_days": 7,
|
||||
"source_type": "rule | instruction",
|
||||
"source_id": "string",
|
||||
"triggering_event_id": "uuid",
|
||||
"activity_definition_id": "string"
|
||||
}
|
||||
```
|
||||
|
||||
Encoded as **JSON** in the message body. `Content-Type: application/json`
|
||||
in the message header.
|
||||
|
||||
This intentionally matches the REST schema so the validator and `_build_issue`
|
||||
logic in `issue_core/api/ingest.py` can be reused unchanged by the NATS
|
||||
consumer.
|
||||
|
||||
## JetStream configuration
|
||||
|
||||
The publisher (e.g. activity-core IssueSink-NATS) writes to a JetStream stream:
|
||||
|
||||
| Field | Value |
|
||||
|---------------|----------------------------------------|
|
||||
| Stream name | `ACT_TASKS` |
|
||||
| Subjects | `act.tasks.>` |
|
||||
| Retention | Limits (Time-based: 7 days) |
|
||||
| Storage | File |
|
||||
| Replicas | 3 in prod, 1 in dev |
|
||||
| Discard | Old (drop oldest on overflow) |
|
||||
| Max msg size | 64 KiB (TaskSpec is small) |
|
||||
|
||||
issue-core consumes via a **durable consumer**:
|
||||
|
||||
| Field | Value |
|
||||
|-----------------|----------------------------------------|
|
||||
| Stream | `ACT_TASKS` |
|
||||
| Consumer name | `issue-core-ingest` |
|
||||
| Filter subject | `act.tasks.create.>` |
|
||||
| Deliver policy | All (catch up from oldest on first start) |
|
||||
| Ack policy | Explicit |
|
||||
| Max deliver | 5 (then dead-letter) |
|
||||
| Ack wait | 30s |
|
||||
| Replay policy | Instant |
|
||||
|
||||
## Idempotency
|
||||
|
||||
NATS JetStream provides **at-least-once** delivery. The consumer must dedupe
|
||||
retries.
|
||||
|
||||
**Idempotency key:** `triggering_event_id` (UUID, included in every payload).
|
||||
|
||||
The consumer's responsibility:
|
||||
|
||||
1. Compute idempotency key from `triggering_event_id`.
|
||||
2. Check whether an issue with that key already exists (lookup by
|
||||
`sync_metadata.ingestion.triggering_event_id`).
|
||||
3. If exists, ack the message without creating a duplicate.
|
||||
4. If not, create the issue and ack.
|
||||
|
||||
Both REST and NATS paths share this dedupe logic, so a task can be safely
|
||||
emitted via either transport without risk of duplicate issues.
|
||||
|
||||
## Implementation plan (when activated)
|
||||
|
||||
1. Add `nats-py>=2.6` as an optional dependency (`pip install issue-core[nats]`).
|
||||
2. New module `issue_core/nats/consumer.py` — connects to NATS, subscribes to
|
||||
the durable consumer, parses messages, calls the same `_build_issue` /
|
||||
backend.create_issue path as the REST endpoint.
|
||||
3. New CLI subcommand `issue subscribe --nats-url ... --stream ACT_TASKS`.
|
||||
4. Add idempotency check to both REST and NATS ingestion paths (single shared
|
||||
function in `issue_core/api/ingest.py` or a new `issue_core/ingestion/`
|
||||
module).
|
||||
5. Tests using `nats-py` test harness or a docker-compose NATS instance.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should the NATS consumer write a `progress_event` to the state hub on each
|
||||
successful ingestion, in addition to creating the issue? Probably yes, but
|
||||
out of scope until activation.
|
||||
- Multi-tenant routing: do we run one issue-core consumer per `target_repo`,
|
||||
or one shared consumer with per-repo backend lookup? Current bias: shared
|
||||
consumer, simpler to operate.
|
||||
- Dead-letter handling: where do messages go after 5 failed deliveries?
|
||||
Candidate: a `ACT_TASKS_DLQ` stream with manual replay tooling.
|
||||
|
||||
## See also
|
||||
|
||||
- `SCOPE.md` — confirms NATS ingestion is in-scope as a future surface.
|
||||
- `issue_core/api/schemas.py` — the canonical `TaskIngestionRequest` schema.
|
||||
- `issue_core/api/ingest.py` — the REST handler whose logic the NATS consumer
|
||||
will share.
|
||||
- activity-core `docs/adr/adr-001-event-bridge-architecture.md` — describes
|
||||
activity-core's migration trajectory from REST to NATS.
|
||||
@@ -1,10 +1,10 @@
|
||||
# Agent Examples
|
||||
|
||||
This directory contains working examples of autonomous agents using the Issue Facade for coordination.
|
||||
This directory contains working examples of autonomous agents using the Issue Core for coordination.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Install issue-facade**:
|
||||
1. **Install issue-core**:
|
||||
```bash
|
||||
cd ../..
|
||||
pip install -e .
|
||||
|
||||
@@ -27,9 +27,9 @@ from typing import Optional, List
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
|
||||
class HumanInLoopAgent:
|
||||
|
||||
@@ -28,9 +28,9 @@ from typing import List, Dict
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
|
||||
class MonitoringAgent:
|
||||
|
||||
@@ -36,9 +36,9 @@ from typing import List
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
|
||||
class BaseAgent:
|
||||
|
||||
@@ -26,9 +26,9 @@ from pathlib import Path
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from issue_tracker.backends.gitea import GiteaBackend
|
||||
from issue_tracker.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.gitea import GiteaBackend
|
||||
from issue_core.core.models import Issue, Label, User, Comment, IssueState
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
|
||||
class SimpleTaskExecutor:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Feedback System Example
|
||||
|
||||
This example demonstrates how to submit feedback about the issue-facade capability.
|
||||
This example demonstrates how to submit feedback about the issue-core capability.
|
||||
|
||||
## Quick Feedback Submission
|
||||
|
||||
@@ -8,7 +8,7 @@ This example demonstrates how to submit feedback about the issue-facade capabili
|
||||
|
||||
```bash
|
||||
# Navigate to the capability directory
|
||||
cd capabilities/issue-facade
|
||||
cd capabilities/issue-core
|
||||
|
||||
# Submit quick text feedback
|
||||
./.capability/feedback submit "The sync command is very slow when dealing with repositories that have 5000+ issues. Would love to see a progress indicator or batch processing to speed this up."
|
||||
@@ -68,17 +68,17 @@ EOF
|
||||
|
||||
### Method 3: From Master Project
|
||||
|
||||
If you're working in a master project that integrates issue-facade:
|
||||
If you're working in a master project that integrates issue-core:
|
||||
|
||||
```bash
|
||||
# From your master project root
|
||||
cd ~/my-master-project
|
||||
|
||||
# Write feedback
|
||||
cat > feedback-for-issue-facade.md << 'EOF'
|
||||
cat > feedback-for-issue-core.md << 'EOF'
|
||||
## Feature Request: GitHub Backend
|
||||
|
||||
We're using issue-facade for our Gitea repos, but our company also has
|
||||
We're using issue-core for our Gitea repos, but our company also has
|
||||
50+ repositories on GitHub. Would love to have a GitHub backend so we
|
||||
can use the same CLI for all our issue tracking.
|
||||
|
||||
@@ -91,8 +91,8 @@ We'd be happy to contribute or test!
|
||||
EOF
|
||||
|
||||
# Copy to capability's feedback directory
|
||||
cp feedback-for-issue-facade.md \
|
||||
capabilities/issue-facade/feedback/inbound/$(date +%Y%m%d)-github-backend.md
|
||||
cp feedback-for-issue-core.md \
|
||||
capabilities/issue-core/feedback/inbound/$(date +%Y%m%d)-github-backend.md
|
||||
```
|
||||
|
||||
## Feedback Categories
|
||||
@@ -286,4 +286,4 @@ If you have questions about the feedback system itself, that's also feedback!
|
||||
|
||||
---
|
||||
|
||||
**Thank you for helping make issue-facade better through your feedback!**
|
||||
**Thank you for helping make issue-core better through your feedback!**
|
||||
|
||||
@@ -65,10 +65,10 @@ If you're integrating this capability into a master project:
|
||||
|
||||
```bash
|
||||
cd my-master-project
|
||||
echo "Feedback about issue-facade..." > feedback.md
|
||||
echo "Feedback about issue-core..." > feedback.md
|
||||
|
||||
# Copy to capability's feedback directory
|
||||
cp feedback.md capabilities/issue-facade/feedback/inbound/$(date +%Y%m%d-%H%M%S)-sync-issue.md
|
||||
cp feedback.md capabilities/issue-core/feedback/inbound/$(date +%Y%m%d-%H%M%S)-sync-issue.md
|
||||
```
|
||||
|
||||
### What to Include
|
||||
@@ -140,7 +140,7 @@ mv feedback/inbound/20251217-feature-request.md feedback/reviewed/
|
||||
Each capability maintains its own feedback directory. Users navigate to the capability and submit feedback.
|
||||
|
||||
```bash
|
||||
cd capabilities/issue-facade
|
||||
cd capabilities/issue-core
|
||||
echo "Feedback..." > feedback/inbound/$(date +%Y%m%d)-feedback.md
|
||||
```
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
"""
|
||||
Universal Issue Tracking System
|
||||
|
||||
A backend-agnostic issue tracking system that supports multiple backends
|
||||
through a plugin architecture. Designed to be extracted into a standalone
|
||||
repository for use across multiple projects.
|
||||
|
||||
Features:
|
||||
- Unified issue model across all backends
|
||||
- Plugin-based backend architecture
|
||||
- Local SQLite backend for offline work
|
||||
- Bidirectional synchronization
|
||||
- CLI-first interface
|
||||
- Support for GitHub-style and other issue tracking systems
|
||||
|
||||
Supported Backends:
|
||||
- Local SQLite (for offline/standalone use)
|
||||
- Gitea (GitHub-compatible API)
|
||||
- Future: GitHub, GitLab, JIRA, Redmine, etc.
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__author__ = "MarkiTect Project"
|
||||
__description__ = "Universal Issue Tracking System with Plugin Architecture"
|
||||
"""
|
||||
issue-core — Authoritative Task Lifecycle Manager
|
||||
|
||||
The single observable place in the Coulomb org where tasks land —
|
||||
regardless of whether they were created by a human, by activity-core,
|
||||
or by an agent. Backend-agnostic via a plugin architecture.
|
||||
|
||||
Features:
|
||||
- Unified issue model across all backends
|
||||
- Plugin-based backend architecture
|
||||
- Local SQLite backend for offline work
|
||||
- Bidirectional synchronization
|
||||
- CLI-first interface
|
||||
- REST ingestion endpoint for activity-core's IssueSink
|
||||
|
||||
Supported Backends:
|
||||
- Local SQLite (offline/standalone)
|
||||
- Gitea (GitHub-compatible API)
|
||||
- Future: GitHub, GitLab, JIRA, Redmine
|
||||
"""
|
||||
|
||||
__version__ = "0.2.0"
|
||||
__author__ = "Coulomb / MarkiTect Project"
|
||||
__description__ = "Authoritative task lifecycle manager with plugin architecture"
|
||||
|
||||
14
issue_core/api/__init__.py
Normal file
14
issue_core/api/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
issue-core REST API.
|
||||
|
||||
Ingestion surface for external emitters — primarily activity-core's
|
||||
IssueSink, which calls POST /issues/ to file tasks against the org's
|
||||
configured backends.
|
||||
|
||||
Run via the CLI: `issue serve --host 0.0.0.0 --port 8765`
|
||||
Requires the [api] extra: `pip install issue-core[api]`.
|
||||
"""
|
||||
|
||||
from .app import create_app
|
||||
|
||||
__all__ = ["create_app"]
|
||||
26
issue_core/api/app.py
Normal file
26
issue_core/api/app.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
FastAPI application factory for issue-core.
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from .. import __version__
|
||||
from .ingest import router as ingest_router
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
app = FastAPI(
|
||||
title="issue-core",
|
||||
description=(
|
||||
"Authoritative task lifecycle manager for the Coulomb org. "
|
||||
"POST /issues/ is the ingestion surface for activity-core's IssueSink."
|
||||
),
|
||||
version=__version__,
|
||||
)
|
||||
app.include_router(ingest_router)
|
||||
|
||||
@app.get("/healthz", tags=["meta"])
|
||||
async def healthz() -> dict:
|
||||
return {"status": "ok", "version": __version__}
|
||||
|
||||
return app
|
||||
66
issue_core/api/auth.py
Normal file
66
issue_core/api/auth.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
API key authentication for the issue-core REST API.
|
||||
|
||||
A single shared key is read from the ISSUE_CORE_API_KEY environment variable.
|
||||
Clients send it either as `Authorization: Bearer <key>` or as `X-API-Key: <key>`.
|
||||
|
||||
If ISSUE_CORE_API_KEY is unset, the server refuses to start — the workplan
|
||||
explicitly forbids an unauthenticated ingestion surface.
|
||||
"""
|
||||
|
||||
import os
|
||||
import secrets
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Header, HTTPException, status
|
||||
|
||||
|
||||
API_KEY_ENV_VAR = "ISSUE_CORE_API_KEY"
|
||||
|
||||
|
||||
class AuthConfigError(RuntimeError):
|
||||
"""Raised at startup when no API key is configured."""
|
||||
|
||||
|
||||
def get_configured_api_key() -> str:
|
||||
key = os.environ.get(API_KEY_ENV_VAR, "").strip()
|
||||
if not key:
|
||||
raise AuthConfigError(
|
||||
f"{API_KEY_ENV_VAR} is not set. The ingestion endpoint requires an API key. "
|
||||
f"Generate one with: python -c 'import secrets; print(secrets.token_urlsafe(32))'"
|
||||
)
|
||||
return key
|
||||
|
||||
|
||||
def _extract_token(
|
||||
authorization: Optional[str],
|
||||
x_api_key: Optional[str],
|
||||
) -> Optional[str]:
|
||||
if x_api_key:
|
||||
return x_api_key.strip()
|
||||
if authorization:
|
||||
scheme, _, value = authorization.partition(" ")
|
||||
if scheme.lower() == "bearer" and value:
|
||||
return value.strip()
|
||||
return None
|
||||
|
||||
|
||||
async def require_api_key(
|
||||
authorization: Optional[str] = Header(default=None),
|
||||
x_api_key: Optional[str] = Header(default=None, alias="X-API-Key"),
|
||||
) -> None:
|
||||
"""FastAPI dependency enforcing the shared API key."""
|
||||
try:
|
||||
expected = get_configured_api_key()
|
||||
except AuthConfigError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=str(exc),
|
||||
)
|
||||
presented = _extract_token(authorization, x_api_key)
|
||||
if not presented or not secrets.compare_digest(presented, expected):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Missing or invalid API key.",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
139
issue_core/api/ingest.py
Normal file
139
issue_core/api/ingest.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
POST /issues/ — task ingestion endpoint.
|
||||
|
||||
Receives a TaskSpec payload (see schemas.TaskIngestionRequest) from an
|
||||
authorized emitter, routes it to the configured backend, and returns the
|
||||
created issue's id and (optional) URL.
|
||||
|
||||
Routing strategy (v1):
|
||||
- Single default backend, looked up via cli.utils.get_default_backend().
|
||||
- target_repo, triggering_event_id, source_*, activity_definition_id are
|
||||
stored on the issue's sync_metadata for traceability back to the emitter.
|
||||
- Per-target-repo routing is a planned follow-up; see SCOPE.md.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from ..backends.gitea import GiteaBackend
|
||||
from ..backends.local import LocalSQLiteBackend
|
||||
from ..cli.utils import get_config_dir, load_backend_configs
|
||||
from ..core.interfaces import BackendFactory, IssueBackend
|
||||
from ..core.models import Issue, IssueState, Label
|
||||
from .auth import require_api_key
|
||||
from .schemas import BackendName, TaskIngestionRequest, TaskIngestionResponse
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
BackendFactory.register_backend("local", LocalSQLiteBackend)
|
||||
BackendFactory.register_backend("gitea", GiteaBackend)
|
||||
|
||||
|
||||
_BACKEND_TYPE_TO_NAME: Dict[str, str] = {
|
||||
"local": "sqlite",
|
||||
"gitea": "gitea",
|
||||
"github": "github",
|
||||
}
|
||||
|
||||
|
||||
def _resolve_backend() -> Tuple[IssueBackend, str]:
|
||||
configs = load_backend_configs()
|
||||
default_name = configs.get("default", "local")
|
||||
if default_name not in configs:
|
||||
if default_name == "local":
|
||||
configs["local"] = {
|
||||
"type": "local",
|
||||
"db_path": str(get_config_dir() / "issues.db"),
|
||||
}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"Default backend '{default_name}' is not configured.",
|
||||
)
|
||||
backend_config = configs[default_name]
|
||||
backend_type = backend_config["type"]
|
||||
try:
|
||||
backend = BackendFactory.create_backend(backend_type)
|
||||
backend.connect(backend_config)
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"Failed to connect to backend '{default_name}': {exc}",
|
||||
)
|
||||
return backend, backend_type
|
||||
|
||||
|
||||
def _build_issue(payload: TaskIngestionRequest, backend_type: str) -> Issue:
|
||||
now = datetime.now(timezone.utc)
|
||||
labels = [Label(name=name) for name in payload.labels]
|
||||
labels.append(Label(name=f"priority:{payload.priority}"))
|
||||
labels.append(Label(name=f"source:{payload.source_type}"))
|
||||
if payload.target_repo:
|
||||
labels.append(Label(name=f"repo:{payload.target_repo}"))
|
||||
|
||||
ingestion_meta: Dict[str, Any] = {
|
||||
"target_repo": payload.target_repo,
|
||||
"source_type": payload.source_type,
|
||||
"source_id": payload.source_id,
|
||||
"triggering_event_id": str(payload.triggering_event_id),
|
||||
"activity_definition_id": payload.activity_definition_id,
|
||||
"ingested_at": now.isoformat(),
|
||||
}
|
||||
if payload.due_in_days is not None:
|
||||
ingestion_meta["due_at"] = (now + timedelta(days=payload.due_in_days)).isoformat()
|
||||
|
||||
sync_metadata: Dict[str, Any] = {"ingestion": ingestion_meta}
|
||||
|
||||
return Issue(
|
||||
id="",
|
||||
number=0,
|
||||
title=payload.title,
|
||||
description=payload.description,
|
||||
state=IssueState.OPEN,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
labels=labels,
|
||||
backend_type=backend_type,
|
||||
sync_metadata=sync_metadata,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/issues/",
|
||||
response_model=TaskIngestionResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_api_key)],
|
||||
summary="Ingest a task from an external emitter (e.g. activity-core).",
|
||||
)
|
||||
async def ingest_task(payload: TaskIngestionRequest) -> TaskIngestionResponse:
|
||||
backend, backend_type = _resolve_backend()
|
||||
draft = _build_issue(payload, backend_type)
|
||||
try:
|
||||
created: Issue = backend.create_issue(draft)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=f"Backend rejected the issue: {exc}",
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
backend.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
issue_id = created.id or str(created.number)
|
||||
issue_url: Optional[str] = None
|
||||
if created.sync_metadata:
|
||||
issue_url = created.sync_metadata.get("url") or created.sync_metadata.get("html_url")
|
||||
backend_name: BackendName = _BACKEND_TYPE_TO_NAME.get(backend_type, backend_type) # type: ignore[assignment]
|
||||
return TaskIngestionResponse(
|
||||
issue_id=issue_id,
|
||||
issue_url=issue_url,
|
||||
backend=backend_name,
|
||||
)
|
||||
50
issue_core/api/schemas.py
Normal file
50
issue_core/api/schemas.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Pydantic schemas for the issue-core REST API.
|
||||
|
||||
The TaskIngestionRequest schema matches activity-core's IssueSink TaskSpec
|
||||
payload exactly. See:
|
||||
- SCOPE.md "TaskSpec payload" section
|
||||
- activity-core docs/adr/adr-001-event-bridge-architecture.md
|
||||
"""
|
||||
|
||||
from typing import List, Literal, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
SourceType = Literal["rule", "instruction"]
|
||||
Priority = Literal["high", "medium", "low"]
|
||||
BackendName = Literal["gitea", "sqlite", "github"]
|
||||
|
||||
|
||||
class TaskIngestionRequest(BaseModel):
|
||||
"""TaskSpec payload from activity-core's IssueSink (POST /issues/)."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
title: str = Field(..., min_length=1, max_length=500)
|
||||
description: str = ""
|
||||
target_repo: str = Field(..., min_length=1)
|
||||
priority: Priority = "medium"
|
||||
labels: List[str] = Field(default_factory=list)
|
||||
due_in_days: Optional[int] = Field(default=None, ge=0)
|
||||
source_type: SourceType
|
||||
source_id: str = Field(..., min_length=1)
|
||||
triggering_event_id: UUID
|
||||
activity_definition_id: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class TaskIngestionResponse(BaseModel):
|
||||
"""Response returned to the emitter after a successful ingestion."""
|
||||
|
||||
issue_id: str
|
||||
issue_url: Optional[str] = None
|
||||
backend: BackendName
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Uniform error envelope."""
|
||||
|
||||
error: str
|
||||
detail: Optional[str] = None
|
||||
@@ -11,11 +11,12 @@ from pathlib import Path
|
||||
from .commands import issue_group
|
||||
from .backend_commands import backend_group
|
||||
from .sync_commands import sync_group
|
||||
from .serve_command import serve_command
|
||||
from .. import __version__
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version=__version__, package_name='issue-tracker')
|
||||
@click.version_option(version=__version__, package_name='issue-core')
|
||||
@click.option('--config', type=click.Path(), help='Configuration file path')
|
||||
@click.option('--backend', help='Backend to use (local, gitea)')
|
||||
@click.option('--verbose', '-v', is_flag=True, help='Verbose output')
|
||||
@@ -52,6 +53,7 @@ def cli(ctx, config, backend, verbose):
|
||||
cli.add_command(issue_group, name='issue')
|
||||
cli.add_command(backend_group, name='backend')
|
||||
cli.add_command(sync_group, name='sync')
|
||||
cli.add_command(serve_command)
|
||||
|
||||
|
||||
# Convenience aliases - direct issue commands
|
||||
|
||||
43
issue_core/cli/serve_command.py
Normal file
43
issue_core/cli/serve_command.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
`issue serve` — launch the issue-core REST API.
|
||||
|
||||
Requires the [api] extra: `pip install issue-core[api]`.
|
||||
"""
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command("serve")
|
||||
@click.option("--host", default="127.0.0.1", show_default=True, help="Bind address.")
|
||||
@click.option("--port", default=8765, show_default=True, type=int, help="Bind port.")
|
||||
@click.option("--reload", is_flag=True, default=False, help="Auto-reload on code change (dev only).")
|
||||
@click.option("--log-level", default="info", show_default=True, help="Uvicorn log level.")
|
||||
def serve_command(host: str, port: int, reload: bool, log_level: str) -> None:
|
||||
"""Launch the issue-core REST API (POST /issues/ ingestion endpoint).
|
||||
|
||||
Requires ISSUE_CORE_API_KEY to be set in the environment.
|
||||
"""
|
||||
try:
|
||||
import uvicorn # noqa: F401
|
||||
except ImportError:
|
||||
raise click.ClickException(
|
||||
"The 'api' extra is not installed. Run: pip install 'issue-core[api]'"
|
||||
)
|
||||
|
||||
from ..api.auth import AuthConfigError, get_configured_api_key
|
||||
|
||||
try:
|
||||
get_configured_api_key()
|
||||
except AuthConfigError as exc:
|
||||
raise click.ClickException(str(exc))
|
||||
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
"issue_core.api.app:create_app",
|
||||
host=host,
|
||||
port=port,
|
||||
reload=reload,
|
||||
log_level=log_level,
|
||||
factory=True,
|
||||
)
|
||||
@@ -3,8 +3,8 @@ requires = ["setuptools>=45", "wheel", "setuptools-scm[toml]>=6.2"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "universal-issue-tracker"
|
||||
description = "Backend-agnostic issue tracking system with plugin architecture"
|
||||
name = "issue-core"
|
||||
description = "Authoritative task lifecycle manager for the Coulomb org — backend-agnostic with plugin architecture"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "MIT"}
|
||||
@@ -45,6 +45,9 @@ dev = [
|
||||
"flake8>=4.0",
|
||||
"mypy>=0.900",
|
||||
"pre-commit>=2.0",
|
||||
"httpx>=0.27",
|
||||
"fastapi>=0.110,<1.0",
|
||||
"pydantic>=2.0,<3.0",
|
||||
]
|
||||
docs = [
|
||||
"sphinx>=4.0",
|
||||
@@ -60,25 +63,30 @@ github = [
|
||||
jira = [
|
||||
"jira>=3.0",
|
||||
]
|
||||
api = [
|
||||
"fastapi>=0.110,<1.0",
|
||||
"uvicorn[standard]>=0.27,<1.0",
|
||||
"pydantic>=2.0,<3.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/markitect/universal-issue-tracker"
|
||||
Documentation = "https://universal-issue-tracker.readthedocs.io/"
|
||||
Repository = "https://github.com/markitect/universal-issue-tracker.git"
|
||||
"Bug Tracker" = "https://github.com/markitect/universal-issue-tracker/issues"
|
||||
Homepage = "https://github.com/coulomb/issue-core"
|
||||
Documentation = "https://issue-core.readthedocs.io/"
|
||||
Repository = "https://github.com/coulomb/issue-core.git"
|
||||
"Bug Tracker" = "https://github.com/coulomb/issue-core/issues"
|
||||
|
||||
[project.scripts]
|
||||
issue = "issue_tracker.cli.main:main"
|
||||
issue-tracker = "issue_tracker.cli.main:main"
|
||||
issue = "issue_core.cli.main:main"
|
||||
issue-core = "issue_core.cli.main:main"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["issue_tracker"]
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["issue_core*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "issue_tracker.__version__"}
|
||||
version = {attr = "issue_core.__version__"}
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
issue_tracker = ["backends/local/schema.sql"]
|
||||
issue_core = ["backends/local/schema.sql"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
@@ -101,7 +109,7 @@ extend-exclude = '''
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 100
|
||||
known_first_party = ["issue_tracker"]
|
||||
known_first_party = ["issue_core"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
@@ -142,7 +150,7 @@ markers = [
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["issue_tracker"]
|
||||
source = ["issue_core"]
|
||||
omit = [
|
||||
"*/tests/*",
|
||||
"*/test_*",
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Test suite for issue-facade capability."""
|
||||
"""Test suite for issue-core capability."""
|
||||
|
||||
177
tests/test_api_ingest.py
Normal file
177
tests/test_api_ingest.py
Normal file
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
Tests for POST /issues/ ingestion endpoint.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("fastapi")
|
||||
pytest.importorskip("httpx")
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from issue_core.api.app import create_app
|
||||
|
||||
|
||||
API_KEY = "test-key-not-a-real-secret-only-for-pytest"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_issue_store(monkeypatch, tmp_path):
|
||||
"""Point cli.utils.get_config_dir at a tmp dir and the default backend at local."""
|
||||
config_dir = tmp_path / "issue-core"
|
||||
config_dir.mkdir()
|
||||
monkeypatch.setattr(
|
||||
"issue_core.cli.utils.get_config_dir", lambda: config_dir, raising=True
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"issue_core.api.ingest.get_config_dir", lambda: config_dir, raising=True
|
||||
)
|
||||
|
||||
db_path = str(config_dir / "issues.db")
|
||||
monkeypatch.setattr(
|
||||
"issue_core.cli.utils.load_backend_configs",
|
||||
lambda: {"default": "local", "local": {"type": "local", "db_path": db_path}},
|
||||
raising=True,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"issue_core.api.ingest.load_backend_configs",
|
||||
lambda: {"default": "local", "local": {"type": "local", "db_path": db_path}},
|
||||
raising=True,
|
||||
)
|
||||
return config_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(monkeypatch, tmp_issue_store):
|
||||
monkeypatch.setenv("ISSUE_CORE_API_KEY", API_KEY)
|
||||
return TestClient(create_app())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def valid_payload():
|
||||
return {
|
||||
"title": "Fix the parser",
|
||||
"description": "Parser fails on multi-line input.",
|
||||
"target_repo": "coulomb/parser",
|
||||
"priority": "high",
|
||||
"labels": ["bug"],
|
||||
"due_in_days": 7,
|
||||
"source_type": "rule",
|
||||
"source_id": "rule:parse-failure",
|
||||
"triggering_event_id": str(uuid.uuid4()),
|
||||
"activity_definition_id": "ad:parser-monitor",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_healthz(client):
|
||||
response = client.get("/healthz")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "ok"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ingest_rejects_missing_api_key(client, valid_payload):
|
||||
response = client.post("/issues/", json=valid_payload)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ingest_rejects_wrong_api_key(client, valid_payload):
|
||||
response = client.post(
|
||||
"/issues/", json=valid_payload, headers={"Authorization": "Bearer wrong"}
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ingest_creates_issue_with_bearer(client, valid_payload):
|
||||
response = client.post(
|
||||
"/issues/",
|
||||
json=valid_payload,
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
)
|
||||
assert response.status_code == 201, response.text
|
||||
body = response.json()
|
||||
assert body["backend"] == "sqlite"
|
||||
assert body["issue_id"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ingest_creates_issue_with_x_api_key(client, valid_payload):
|
||||
response = client.post(
|
||||
"/issues/",
|
||||
json=valid_payload,
|
||||
headers={"X-API-Key": API_KEY},
|
||||
)
|
||||
assert response.status_code == 201, response.text
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ingest_rejects_invalid_payload(client):
|
||||
bad = {"title": "no required fields"}
|
||||
response = client.post(
|
||||
"/issues/", json=bad, headers={"Authorization": f"Bearer {API_KEY}"}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ingest_rejects_invalid_priority(client, valid_payload):
|
||||
valid_payload["priority"] = "urgent"
|
||||
response = client.post(
|
||||
"/issues/",
|
||||
json=valid_payload,
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_ingest_persists_traceability_metadata(client, valid_payload, tmp_issue_store):
|
||||
"""Check that triggering_event_id, source_*, target_repo are stored on the issue."""
|
||||
response = client.post(
|
||||
"/issues/",
|
||||
json=valid_payload,
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
)
|
||||
assert response.status_code == 201, response.text
|
||||
issue_id = response.json()["issue_id"]
|
||||
|
||||
from issue_core.backends.local import LocalSQLiteBackend
|
||||
|
||||
backend = LocalSQLiteBackend()
|
||||
backend.connect({"type": "local", "db_path": str(tmp_issue_store / "issues.db")})
|
||||
try:
|
||||
stored = backend.get_issue(issue_id)
|
||||
assert stored is not None
|
||||
ingestion = stored.sync_metadata.get("ingestion") or {}
|
||||
assert ingestion["target_repo"] == valid_payload["target_repo"]
|
||||
assert ingestion["source_type"] == valid_payload["source_type"]
|
||||
assert ingestion["source_id"] == valid_payload["source_id"]
|
||||
assert ingestion["triggering_event_id"] == valid_payload["triggering_event_id"]
|
||||
assert ingestion["activity_definition_id"] == valid_payload["activity_definition_id"]
|
||||
label_names = {label.name for label in stored.labels}
|
||||
assert f"priority:{valid_payload['priority']}" in label_names
|
||||
assert f"source:{valid_payload['source_type']}" in label_names
|
||||
assert f"repo:{valid_payload['target_repo']}" in label_names
|
||||
assert "bug" in label_names
|
||||
finally:
|
||||
backend.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_app_refuses_without_api_key_env(monkeypatch, tmp_issue_store, valid_payload):
|
||||
monkeypatch.delenv("ISSUE_CORE_API_KEY", raising=False)
|
||||
app = create_app()
|
||||
client = TestClient(app)
|
||||
response = client.post(
|
||||
"/issues/", json=valid_payload, headers={"Authorization": "Bearer anything"}
|
||||
)
|
||||
assert response.status_code == 503
|
||||
assert "ISSUE_CORE_API_KEY" in response.json()["detail"]
|
||||
@@ -12,8 +12,8 @@ from pathlib import Path
|
||||
from click.testing import CliRunner
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
|
||||
from issue_tracker.cli.main import cli
|
||||
from issue_tracker.cli.utils import load_backend_configs, save_backend_configs
|
||||
from issue_core.cli.main import cli
|
||||
from issue_core.cli.utils import load_backend_configs, save_backend_configs
|
||||
|
||||
|
||||
class TestCLICommands:
|
||||
@@ -37,9 +37,9 @@ class TestCLICommands:
|
||||
assert result.exit_code == 0
|
||||
# Should show either configured backends or "No backends configured"
|
||||
|
||||
@patch('issue_tracker.cli.backend_commands.load_backend_configs')
|
||||
@patch('issue_tracker.cli.backend_commands.save_backend_configs')
|
||||
@patch('issue_tracker.cli.backend_commands.test_backend_connection')
|
||||
@patch('issue_core.cli.backend_commands.load_backend_configs')
|
||||
@patch('issue_core.cli.backend_commands.save_backend_configs')
|
||||
@patch('issue_core.cli.backend_commands.test_backend_connection')
|
||||
def test_backend_add_gitea_with_env_token(self, mock_test_conn, mock_save, mock_load):
|
||||
"""Test adding Gitea backend with environment token."""
|
||||
# Mock empty initial config
|
||||
@@ -63,9 +63,9 @@ class TestCLICommands:
|
||||
assert saved_config['test-gitea']['type'] == 'gitea'
|
||||
assert saved_config['test-gitea']['token'] == 'test-token'
|
||||
|
||||
@patch('issue_tracker.cli.backend_commands.load_backend_configs')
|
||||
@patch('issue_tracker.cli.backend_commands.save_backend_configs')
|
||||
@patch('issue_tracker.cli.backend_commands.test_backend_connection')
|
||||
@patch('issue_core.cli.backend_commands.load_backend_configs')
|
||||
@patch('issue_core.cli.backend_commands.save_backend_configs')
|
||||
@patch('issue_core.cli.backend_commands.test_backend_connection')
|
||||
def test_backend_add_local(self, mock_test_conn, mock_save, mock_load):
|
||||
"""Test adding local backend."""
|
||||
mock_load.return_value = {}
|
||||
@@ -78,7 +78,7 @@ class TestCLICommands:
|
||||
assert result.exit_code == 0
|
||||
assert 'Backend \'test-local\' added successfully' in result.output
|
||||
|
||||
@patch('issue_tracker.cli.commands.get_backend')
|
||||
@patch('issue_core.cli.commands.get_backend')
|
||||
def test_show_command(self, mock_get_backend):
|
||||
"""Test issue show command."""
|
||||
# Mock backend and issue
|
||||
@@ -104,7 +104,7 @@ class TestCLICommands:
|
||||
assert 'Test description' in result.output
|
||||
assert 'State: open' in result.output
|
||||
|
||||
@patch('issue_tracker.cli.utils.get_backend')
|
||||
@patch('issue_core.cli.utils.get_backend')
|
||||
def test_show_command_issue_not_found(self, mock_get_backend):
|
||||
"""Test issue show command when issue doesn't exist."""
|
||||
mock_backend = Mock()
|
||||
@@ -121,7 +121,7 @@ class TestCLICommands:
|
||||
result = self.runner.invoke(cli, ['--version'])
|
||||
assert result.exit_code == 0
|
||||
|
||||
@patch('issue_tracker.cli.utils.get_backend')
|
||||
@patch('issue_core.cli.utils.get_backend')
|
||||
def test_list_command_basic(self, mock_get_backend):
|
||||
"""Test basic list command functionality."""
|
||||
# This test will help us identify the existing bug
|
||||
@@ -157,7 +157,7 @@ class TestBackendConfiguration:
|
||||
|
||||
def test_config_directory_creation(self):
|
||||
"""Test configuration directory is created properly."""
|
||||
from issue_tracker.cli.utils import get_config_dir
|
||||
from issue_core.cli.utils import get_config_dir
|
||||
|
||||
config_dir = get_config_dir()
|
||||
assert config_dir.exists()
|
||||
@@ -177,7 +177,7 @@ class TestBackendConfiguration:
|
||||
}
|
||||
|
||||
# Test saving
|
||||
with patch('issue_tracker.cli.utils.get_backend_config_path', return_value=config_file):
|
||||
with patch('issue_core.cli.utils.get_backend_config_path', return_value=config_file):
|
||||
save_backend_configs(test_config)
|
||||
|
||||
# Test loading
|
||||
@@ -190,7 +190,7 @@ class TestBackendConfiguration:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
non_existent_file = Path(temp_dir) / 'nonexistent.json'
|
||||
|
||||
with patch('issue_tracker.cli.utils.get_backend_config_path', return_value=non_existent_file):
|
||||
with patch('issue_core.cli.utils.get_backend_config_path', return_value=non_existent_file):
|
||||
config = load_backend_configs()
|
||||
|
||||
assert config == {}
|
||||
@@ -204,13 +204,13 @@ class TestEnvironmentTokenDetection:
|
||||
"""Test GITEA_API_TOKEN environment variable detection."""
|
||||
mock_getenv.return_value = 'test-env-token'
|
||||
|
||||
from issue_tracker.cli.backend_commands import add_backend
|
||||
from issue_core.cli.backend_commands import add_backend
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('issue_tracker.cli.backend_commands.load_backend_configs', return_value={}):
|
||||
with patch('issue_tracker.cli.backend_commands.save_backend_configs'):
|
||||
with patch('issue_tracker.cli.backend_commands.test_backend_connection', return_value=True):
|
||||
with patch('issue_core.cli.backend_commands.load_backend_configs', return_value={}):
|
||||
with patch('issue_core.cli.backend_commands.save_backend_configs'):
|
||||
with patch('issue_core.cli.backend_commands.test_backend_connection', return_value=True):
|
||||
result = runner.invoke(add_backend, [
|
||||
'test-gitea', 'gitea'
|
||||
], input='https://git.example.com\ntestorg\ntestrepo\n')
|
||||
|
||||
@@ -7,7 +7,7 @@ including state management, validation, and business logic.
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
from issue_tracker.core.models import (
|
||||
from issue_core.core.models import (
|
||||
Issue, Label, User, Milestone, Comment,
|
||||
IssueState, Priority, IssueType, LabelCategories
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ These tests ensure the Gitea backend works correctly with the API.
|
||||
import pytest
|
||||
import json
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from issue_tracker.backends.gitea.backend import GiteaBackend, GiteaAPIError
|
||||
from issue_core.backends.gitea.backend import GiteaBackend, GiteaAPIError
|
||||
|
||||
|
||||
class TestGiteaBackend:
|
||||
@@ -32,7 +32,7 @@ class TestGiteaBackend:
|
||||
assert self.backend.repo is None
|
||||
assert self.backend.session is not None
|
||||
|
||||
@patch('issue_tracker.backends.gitea.backend.requests.Session')
|
||||
@patch('issue_core.backends.gitea.backend.requests.Session')
|
||||
def test_connect_success(self, mock_session_class):
|
||||
"""Test successful connection to Gitea API."""
|
||||
mock_session = MagicMock()
|
||||
@@ -61,7 +61,7 @@ class TestGiteaBackend:
|
||||
'Accept': 'application/json'
|
||||
})
|
||||
|
||||
@patch('issue_tracker.backends.gitea.backend.requests.Session')
|
||||
@patch('issue_core.backends.gitea.backend.requests.Session')
|
||||
def test_connect_failure(self, mock_session_class):
|
||||
"""Test failed connection raises appropriate error."""
|
||||
mock_session = MagicMock()
|
||||
@@ -96,7 +96,7 @@ 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'
|
||||
|
||||
@patch('issue_tracker.backends.gitea.backend.requests.Session')
|
||||
@patch('issue_core.backends.gitea.backend.requests.Session')
|
||||
def test_test_connection_success(self, mock_session_class):
|
||||
"""Test test_connection method works correctly."""
|
||||
mock_session = MagicMock()
|
||||
@@ -117,7 +117,7 @@ class TestGiteaBackend:
|
||||
result = backend.test_connection()
|
||||
assert result is True
|
||||
|
||||
@patch('issue_tracker.backends.gitea.backend.requests.Session')
|
||||
@patch('issue_core.backends.gitea.backend.requests.Session')
|
||||
def test_test_connection_failure(self, mock_session_class):
|
||||
"""Test test_connection handles failures gracefully."""
|
||||
mock_session = MagicMock()
|
||||
@@ -139,7 +139,7 @@ class TestGiteaBackend:
|
||||
result = backend.test_connection()
|
||||
assert result is False
|
||||
|
||||
@patch('issue_tracker.backends.gitea.backend.requests.Session')
|
||||
@patch('issue_core.backends.gitea.backend.requests.Session')
|
||||
def test_get_issue_success(self, mock_session_class):
|
||||
"""Test successful issue retrieval."""
|
||||
mock_session = MagicMock()
|
||||
|
||||
@@ -10,9 +10,9 @@ import tempfile
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from issue_tracker.backends.local.backend import LocalSQLiteBackend
|
||||
from issue_tracker.core.models import Issue, Label, User, Milestone, Comment, IssueState
|
||||
from issue_tracker.core.interfaces import IssueFilter
|
||||
from issue_core.backends.local.backend import LocalSQLiteBackend
|
||||
from issue_core.core.models import Issue, Label, User, Milestone, Comment, IssueState
|
||||
from issue_core.core.interfaces import IssueFilter
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
|
||||
@@ -2,34 +2,34 @@
|
||||
id: ISSC-WP-0001
|
||||
type: workplan
|
||||
domain: custodian
|
||||
repo: issue-facade
|
||||
status: active
|
||||
repo: issue-core
|
||||
status: done
|
||||
state_hub_workstream_id: 1135fc1d-1f46-4e35-886d-04cc3b8050b6
|
||||
tasks:
|
||||
- id: T01
|
||||
title: Rename package issue-facade → issue-core throughout
|
||||
state_hub_task_id: b7054428-82a9-4d81-bfa8-5b5ee2eaf69f
|
||||
status: todo
|
||||
status: done
|
||||
- id: T02
|
||||
title: Register issue-core in state hub under capabilities domain
|
||||
state_hub_task_id: b1d36996-44ff-48b9-b208-709d6874453c
|
||||
status: todo
|
||||
status: done
|
||||
- id: T03
|
||||
title: Write INTENT.md
|
||||
state_hub_task_id: 265c6338-0310-409d-a081-6446042f6274
|
||||
status: todo
|
||||
status: done
|
||||
- id: T04
|
||||
title: Update or write SCOPE.md
|
||||
state_hub_task_id: f95ac730-7ba0-4eae-bcae-de1e7d24b164
|
||||
status: todo
|
||||
status: done
|
||||
- id: T05
|
||||
title: Implement task ingestion REST endpoint POST /issues/
|
||||
state_hub_task_id: 26af07e4-c072-42ad-bb5c-facb196156c9
|
||||
status: todo
|
||||
status: done
|
||||
- id: T06
|
||||
title: Document NATS subscriber interface (design stub)
|
||||
state_hub_task_id: dff61fed-1e8c-4eb3-bbd6-1e3742329945
|
||||
status: todo
|
||||
status: done
|
||||
created: "2026-05-14"
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user