diff --git a/.capability/README.md b/.capability/README.md
index 0185665..c7d1543 100644
--- a/.capability/README.md
+++ b/.capability/README.md
@@ -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)
diff --git a/.capability/agent-context.md b/.capability/agent-context.md
index 5f9a9f6..a22f70c 100644
--- a/.capability/agent-context.md
+++ b/.capability/agent-context.md
@@ -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
diff --git a/.capability/integrate.sh b/.capability/integrate.sh
index b28d556..3e3e75b 100755
--- a/.capability/integrate.sh
+++ b/.capability/integrate.sh
@@ -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)
diff --git a/.capability/integration-checklist.md b/.capability/integration-checklist.md
index 601e2fa..d0ba8e8 100644
--- a/.capability/integration-checklist.md
+++ b/.capability/integration-checklist.md
@@ -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**
diff --git a/AGENT_INTEGRATION.md b/AGENT_INTEGRATION.md
index a6c1640..3839ffd 100644
--- a/AGENT_INTEGRATION.md
+++ b/AGENT_INTEGRATION.md
@@ -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 < 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)
diff --git a/CAPABILITY-issue-tracking.yaml b/CAPABILITY-issue-tracking.yaml
index b1344dc..0093c29 100644
--- a/CAPABILITY-issue-tracking.yaml
+++ b/CAPABILITY-issue-tracking.yaml
@@ -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:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1602365..72e6252 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/CLAUDE.md b/CLAUDE.md
index 759bcce..75b9e16 100644
--- a/CLAUDE.md
+++ b/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//`
+1. Create backend package in `issue_core/backends//`
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
diff --git a/INTENT.md b/INTENT.md
new file mode 100644
index 0000000..5d44e86
--- /dev/null
+++ b/INTENT.md
@@ -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/`.
diff --git a/Makefile b/Makefile
index a2bbf2f..ab44021 100644
--- a/Makefile
+++ b/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
diff --git a/README.md b/README.md
index 7b7b8a6..e3a9a58 100644
--- a/README.md
+++ b/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 ` or `X-API-Key: `.
+See `SCOPE.md` "TaskSpec payload" for the request schema, or visit
+`http://:/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`)
diff --git a/ROADMAP.md b/ROADMAP.md
index 6b42727..46d8c5a 100644
--- a/ROADMAP.md
+++ b/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:
diff --git a/ReusableCapabilitiesArchitecture.md b/ReusableCapabilitiesArchitecture.md
index 1b83ae9..3b1d2d2 100644
--- a/ReusableCapabilitiesArchitecture.md
+++ b/ReusableCapabilitiesArchitecture.md
@@ -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 _` 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
```
diff --git a/SCOPE.md b/SCOPE.md
new file mode 100644
index 0000000..d793d29
--- /dev/null
+++ b/SCOPE.md
@@ -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.
diff --git a/docs/nats-task-ingestion.md b/docs/nats-task-ingestion.md
new file mode 100644
index 0000000..279fc3c
--- /dev/null
+++ b/docs/nats-task-ingestion.md
@@ -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.
diff --git a/examples/agents/README.md b/examples/agents/README.md
index 85ee962..6798ade 100644
--- a/examples/agents/README.md
+++ b/examples/agents/README.md
@@ -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 .
diff --git a/examples/agents/human_in_loop.py b/examples/agents/human_in_loop.py
index b848899..dddd0de 100755
--- a/examples/agents/human_in_loop.py
+++ b/examples/agents/human_in_loop.py
@@ -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:
diff --git a/examples/agents/monitoring_agent.py b/examples/agents/monitoring_agent.py
index b5c6fc6..719f6ea 100755
--- a/examples/agents/monitoring_agent.py
+++ b/examples/agents/monitoring_agent.py
@@ -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:
diff --git a/examples/agents/multi_agent_pipeline.py b/examples/agents/multi_agent_pipeline.py
index 25ef8e8..ecdebda 100755
--- a/examples/agents/multi_agent_pipeline.py
+++ b/examples/agents/multi_agent_pipeline.py
@@ -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:
diff --git a/examples/agents/simple_task_executor.py b/examples/agents/simple_task_executor.py
index 30f99a5..121db62 100755
--- a/examples/agents/simple_task_executor.py
+++ b/examples/agents/simple_task_executor.py
@@ -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:
diff --git a/examples/feedback-example.md b/examples/feedback-example.md
index ed9efb2..1b4dd62 100644
--- a/examples/feedback-example.md
+++ b/examples/feedback-example.md
@@ -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!**
diff --git a/feedback/README.md b/feedback/README.md
index f2075b4..ab5b2fb 100644
--- a/feedback/README.md
+++ b/feedback/README.md
@@ -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
```
diff --git a/issue_core/__init__.py b/issue_core/__init__.py
index cab4613..e9a0eee 100644
--- a/issue_core/__init__.py
+++ b/issue_core/__init__.py
@@ -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"
\ No newline at end of file
+"""
+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"
diff --git a/issue_core/api/__init__.py b/issue_core/api/__init__.py
new file mode 100644
index 0000000..af0fb6b
--- /dev/null
+++ b/issue_core/api/__init__.py
@@ -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"]
diff --git a/issue_core/api/app.py b/issue_core/api/app.py
new file mode 100644
index 0000000..c21aff4
--- /dev/null
+++ b/issue_core/api/app.py
@@ -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
diff --git a/issue_core/api/auth.py b/issue_core/api/auth.py
new file mode 100644
index 0000000..548232d
--- /dev/null
+++ b/issue_core/api/auth.py
@@ -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 ` or as `X-API-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"},
+ )
diff --git a/issue_core/api/ingest.py b/issue_core/api/ingest.py
new file mode 100644
index 0000000..c2faaf7
--- /dev/null
+++ b/issue_core/api/ingest.py
@@ -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,
+ )
diff --git a/issue_core/api/schemas.py b/issue_core/api/schemas.py
new file mode 100644
index 0000000..bcd9fed
--- /dev/null
+++ b/issue_core/api/schemas.py
@@ -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
diff --git a/issue_core/cli/main.py b/issue_core/cli/main.py
index a6e8cb9..dafab62 100644
--- a/issue_core/cli/main.py
+++ b/issue_core/cli/main.py
@@ -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
diff --git a/issue_core/cli/serve_command.py b/issue_core/cli/serve_command.py
new file mode 100644
index 0000000..d8651bd
--- /dev/null
+++ b/issue_core/cli/serve_command.py
@@ -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,
+ )
diff --git a/pyproject.toml b/pyproject.toml
index 72166b2..df79bb6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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_*",
diff --git a/tests/__init__.py b/tests/__init__.py
index c3ae855..23cebb9 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +1 @@
-"""Test suite for issue-facade capability."""
\ No newline at end of file
+"""Test suite for issue-core capability."""
diff --git a/tests/test_api_ingest.py b/tests/test_api_ingest.py
new file mode 100644
index 0000000..de9d1d7
--- /dev/null
+++ b/tests/test_api_ingest.py
@@ -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"]
diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py
index f05d481..b2d6af8 100644
--- a/tests/test_cli_commands.py
+++ b/tests/test_cli_commands.py
@@ -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')
diff --git a/tests/test_core_models.py b/tests/test_core_models.py
index 9cb3403..4fdf578 100644
--- a/tests/test_core_models.py
+++ b/tests/test_core_models.py
@@ -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
)
diff --git a/tests/test_gitea_backend.py b/tests/test_gitea_backend.py
index 0a7338c..73df749 100644
--- a/tests/test_gitea_backend.py
+++ b/tests/test_gitea_backend.py
@@ -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()
diff --git a/tests/test_local_backend.py b/tests/test_local_backend.py
index 64d829f..4dc155e 100644
--- a/tests/test_local_backend.py
+++ b/tests/test_local_backend.py
@@ -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
diff --git a/workplans/ISSC-WP-0001-rename-and-task-ingestion.md b/workplans/ISSC-WP-0001-rename-and-task-ingestion.md
index 0c0cdb1..b9642ff 100644
--- a/workplans/ISSC-WP-0001-rename-and-task-ingestion.md
+++ b/workplans/ISSC-WP-0001-rename-and-task-ingestion.md
@@ -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"
---