feat(SCOPONE-0010): vendor agent assets and clean docs

This commit is contained in:
Giancarmine Salucci
2026-04-10 22:35:01 +02:00
parent a4e2891c87
commit 5370876db3
32 changed files with 4046 additions and 81 deletions

1
.github/skills vendored
View File

@@ -1 +0,0 @@
/home/moze/Sources/copilot-agents/.github/skills

View File

@@ -0,0 +1,71 @@
---
name: agent-conventions
description: Shared conventions for the JIRA agent pipeline — mandatory behavioral rules and error handling. Load this skill whenever executing as part of the Orchestrator workflow (Interpreter, Initializer, Planner, Splitter, Developer, Reviewer).
---
# Agent Conventions
These conventions apply to ALL agents in the pipeline.
## Mandatory Behavioral Rules
1. **TOOL USE IS MANDATORY**: You MUST invoke actual tool calls. NEVER narrate, simulate, or describe tool usage without calling the tool. If you write "I read the file..." you MUST have a corresponding tool invocation. Narrating without invoking is a CRITICAL ERROR.
2. **STEP ORDER**: Execute YOUR agent's numbered steps in order. Skipping a step is a CRITICAL ERROR. If a step fails, STOP and report — do NOT skip. Note: this applies to steps within YOUR agent only — the Orchestrator may launch multiple agents in parallel, which is expected and correct.
3. **ACTUAL DATA ONLY**: Base ALL decisions on data returned by tool calls, NOT on assumptions or pre-training knowledge. If a tool returns unexpected results, trust the tool output.
4. **NO FABRICATION**: NEVER fabricate build results, test outputs, file contents, agent responses, or success statuses. If you cannot verify something, report it as unverified.
5. **VARIABLE SUBSTITUTION**: When calling `runSubagent()`, you MUST substitute ALL `{placeholder}` values with actual data before the call. NEVER pass literal placeholder strings. Every subagent runs in an isolated context with NO access to your variables.
6. **WORKSPACE-LOCAL TEMP FILES**: All temporary files MUST be written under `prompts/{jira}/tmp/`. NEVER use OS temp directories (`/tmp`, `$TMPDIR`, `%TEMP%`, `os.tmpdir()`). This ensures all pipeline artifacts stay within the workspace and are discoverable for debugging. Clean up `tmp/` contents at pipeline completion if desired, but NEVER delete files outside the workspace.
7. **PROGRAMMATIC FIRST**: If a step is deterministic bookkeeping, artifact scanning, field extraction, diff comparison, count aggregation, progress math, ETA estimation, command detection, or schema-like validation, do it with direct tools and local file state. Do NOT spend an LLM or subagent call on work that can be executed programmatically.
## Error Convention
On ANY error: (1) Log with level ERROR, (2) Report clearly with `ERROR: {Agent} failed at Step <N> — <reason>. CANNOT PROCEED.`, (3) STOP immediately.
On a non-critical issue (e.g., optional file missing, stale cache, non-blocking lint warning): (1) Log with level WARNING, (2) Report with `WARNING: {Agent} at Step <N> — <reason>. Continuing.`, (3) Continue execution. Use WARNING only when the issue does not compromise correctness.
If an agent call fails, retry ONCE. If it fails again: `FATAL: {agent} failed twice. Manual debugging required. STOPPING.`
After every `runSubagent()` call, verify the expected output file exists by reading it. If missing: `ERROR: {agent} did not create {expected_file}. STOPPING.`
## Timestamps
Use ISO8601 format (`YYYY-MM-DDTHH:mm:ss.sssZ`) for all timestamps everywhere.
## Role-Specific Constraints
### Orchestrator Constraints
FORBIDDEN: Code editing, planning decisions, implementation, testing, reviewing
ALLOWED: Agent coordination, programmatic startup analysis, progress tracking, ETA calculation, git management, user communication, direct Developer invocation for trivial-graph fast path, programmatic build/test validation for trivial-graph auto-accept
### Interpreter Constraints
FORBIDDEN: Code analysis, planning, implementation, git operations
ALLOWED: Prompt refinement, JIRA extraction, work type detection, slug generation
### Initializer Constraints
FORBIDDEN: Planning, coding, reviewing, user interaction
ALLOWED: Codebase analysis, documentation creation
### Planner Constraints
FORBIDDEN: Code editing, implementation, reviewing
ALLOWED: Research, analysis, planning, documentation updates, task decomposition into tasks.yaml, executing planner-assigned tool mandates
### Splitter Constraints
FORBIDDEN: Code editing, planning decisions, implementation, reviewing
ALLOWED: Task decomposition, validation, parallelization mapping
### task-executor Constraints
FORBIDDEN: Planning, research, architecture decisions, git commits
ALLOWED: Developer coordination, lightweight deterministic validation in PARALLEL mode, Reviewer coordination in SEQUENTIAL mode, task-state updates in tasks.yaml
### Developer Constraints
FORBIDDEN: Planning, researching, reviewing, git commits, design decisions
ALLOWED: Code implementation, task-local sanity checks in PARALLEL mode, build/test execution per task spec in SEQUENTIAL mode
### Reviewer Constraints
FORBIDDEN: Code editing, planning, implementation, research
ALLOWED: Quality validation, acceptance/rejection decisions, final prompt-review ownership
## Emergency Stop Mechanism
If you realize you're about to perform a forbidden action, immediately return:
"ROLE VIOLATION: Attempted {forbidden_action}. Self-terminating per conventions."

85
.github/skills/append-log/SKILL.md vendored Normal file
View File

@@ -0,0 +1,85 @@
```skill
---
name: append-log
description: Appends a structured JSON log entry to the pipeline log file. Use this skill whenever a Log instruction appears in the Orchestrator.
argument-hint: event=<name> message=<text> iteration=<n|null> task_id=<id|null> parallel_group=<id|null>
---
# Append Log
## Log File
Path: `prompts/{jira}/log.jsonl` — use absolute path always.
## Instruction: `append-log`
`append-log` is a SKILL procedure. Skills provide instructions; tools execute actions.
Use available timestamp and file-edit tools to execute this procedure.
If logging fails, treat it as a loading/configuration issue (skill loading, tool availability, or path mismatch), not as permission to bypass logging.
When you see `Log:` followed by an event name, do these steps:
### Step 1: Get Timestamp
Follow the [timestamp](../timestamp/SKILL.md) skill to get `{ISO8601}`.
### Step 2: Build JSON Line
```
{"timestamp":"{ISO8601}","level":"{level}","agent":"Orchestrator","event":"{event}","iteration":{iteration},"task_id":{task_id},"parallel_group":{parallel_group},"message":"{message}"}
```
- `level`: `"INFO"` (default) or `"ERROR"`
- `iteration`: integer or `null`
- `task_id`: `"TASK-X"` or `null`
- `parallel_group`: `"A"` or `null`
### Step 2.5: Violation Detection
If `message` contains "ERROR.*violates.*rule" or "FORBIDDEN.*action" or "ROLE VIOLATION":
Set `level`: `"CRITICAL_VIOLATION"`
Add field: `"role_violation": true`
### Step 3: Append to File
Use deterministic file operations:
1. Resolve absolute path: `{absolute_path}/prompts/{jira}/log.jsonl`.
2. If file does not exist, create it with exactly `{json_line}\n`.
3. If file exists, append exactly one newline-terminated JSON line at end of file using the available edit mechanism for the environment.
4. Preserve existing content verbatim; never rewrite prior entries except appending at EOF.
Implementation guidance by capability:
- If a direct append API exists, use it.
- Otherwise read current content and write back `existing_content + "\n" + json_line + "\n"` (avoid duplicate blank lines).
- Prefer file-edit tools over terminal commands.
### Step 4: Diagnostics Before Fallback
Before any fallback behavior, verify:
1. Skill discovery works (skill folder and frontmatter `name` are both `append-log`).
2. Skill loading is enabled and visible in chat customization diagnostics.
3. Required file-edit tools are available to the active agent.
If diagnostics indicate missing skill loading/configuration, STOP and report the configuration issue explicitly.
## Example
First `Log:` creates the file:
```
create_file(
filePath = "F:/project/prompts/CACTUS-1234/log.jsonl",
content = '{"timestamp":"2026-02-11T14:30:00.000Z","level":"INFO","agent":"Orchestrator","event":"orchestrator.start","iteration":null,"task_id":null,"parallel_group":null,"message":"Pipeline started for CACTUS-1234"}\n'
)
```
Second `Log:` appends a new line to the same file:
```
# Pseudocode (tool names vary by environment):
# read current file content
# append one JSON line at EOF
# write back without altering previous lines
```
```

112
.github/skills/content-hash/SKILL.md vendored Normal file
View File

@@ -0,0 +1,112 @@
---
name: content-hash
description: OS-aware content hashing for files and workspace fingerprints using built-in system tools only.
---
# Content Hash
Use this skill when an agent needs deterministic hashes for files or a workspace fingerprint without external dependencies.
## Protocol
Use only tools that are present by default on the target OS.
Do not require Python, Node.js, or external packages.
Prefer `md5`-compatible output across all platforms.
## Detect OS
Select the command family by OS:
1. Linux: `md5sum`
2. macOS: `md5`
3. Windows: PowerShell `Get-FileHash -Algorithm MD5`
If the agent already knows the OS from runtime context, use it directly.
## File Hash
### Linux
```sh
md5sum "docs/ARCHITECTURE.md" | awk '{print $1}'
```
### macOS
```sh
md5 -q "docs/ARCHITECTURE.md"
```
### Windows PowerShell
```powershell
(Get-FileHash -Algorithm MD5 'docs/ARCHITECTURE.md').Hash.ToLower()
```
## Workspace Fingerprint
To compute a workspace fingerprint, hash a deterministic manifest of relevant files.
Sort paths before hashing.
Include both relative path and file content digest in the manifest.
### Linux
```sh
{
for path in $(printf '%s\n' docs/ARCHITECTURE.md docs/CODE_STYLE.md docs/FINDINGS.md | sort); do
if [ -f "$path" ]; then
printf '%s:' "$path"
md5sum "$path" | awk '{print $1}'
fi
done
} | md5sum | awk '{print $1}'
```
### macOS
```sh
{
for path in $(printf '%s\n' docs/ARCHITECTURE.md docs/CODE_STYLE.md docs/FINDINGS.md | sort); do
if [ -f "$path" ]; then
printf '%s:' "$path"
md5 -q "$path"
printf '\n'
fi
done
} | md5 -q
```
### Windows PowerShell
```powershell
$paths = @('docs/ARCHITECTURE.md', 'docs/CODE_STYLE.md', 'docs/FINDINGS.md') | Sort-Object
$manifest = foreach ($path in $paths) {
if (Test-Path $path) {
$hash = (Get-FileHash -Algorithm MD5 $path).Hash.ToLower()
"${path}:$hash"
}
}
$bytes = [System.Text.Encoding]::UTF8.GetBytes(($manifest -join "`n"))
$stream = [System.IO.MemoryStream]::new(,$bytes)
try {
(Get-FileHash -Algorithm MD5 -InputStream $stream).Hash.ToLower()
} finally {
$stream.Dispose()
}
```
## Output Normalization
Normalize all emitted hashes to lowercase hexadecimal.
Do not include filenames, labels, or surrounding prose in the stored hash fields.
## Error Handling
If the OS-native hashing command is unavailable on the current platform:
1. report that hash evidence could not be computed
2. set the relevant trust state to `unknown`
3. write a concrete reason naming the missing hashing command
If a file is missing, do not fabricate a hash.
Only hash files that actually exist.

View File

@@ -0,0 +1,43 @@
# Context7 Lookup Skill
Fetches up-to-date, version-specific library documentation via Context7 MCP server (68,000+ libraries). **Fully language-agnostic** - works with Node.js, Python, Java, Rust, Go, Ruby, PHP, C#, Swift, and more.
## Purpose
Prevent hallucinated APIs and outdated code examples by fetching current documentation from official sources.
## Auto-detects Versions From
Node.js • Python • Java • Rust • Ruby • Go • PHP • C#/.NET • Swift
(package.json, requirements.txt, Cargo.toml, pom.xml, Gemfile, go.mod, composer.json, *.csproj, Podfile)
## Integration
- **Planner**: Research phase (Step 4) for library investigation
- **Developer**: Pre-implementation (Step 2.5) to verify APIs
- **Reviewer**: Optional verification during code review
## How It Works
1. **Detect Version**: `grep_search` dependency files
2. **Resolve Library**: `mcp_context7_resolve-library-id`
3. **Fetch Docs**: `mcp_context7_query-docs`
4. **Apply**: Use verified API patterns
## Examples
**Python**: Django ORM • Flask routes • SQLAlchemy
**Rust**: Tokio async • Serde serialization
**Java**: Spring Boot • Hibernate
**JS/TS**: Next.js • React • Express
**Go**: Gin router • GORM
## Benefits
✅ Current, version-specific docs
✅ No hallucinated APIs
✅ Official examples
✅ Prevents "method not found" errors
See [SKILL.md](SKILL.md) for complete documentation.

149
.github/skills/context7-lookup/SKILL.md vendored Normal file
View File

@@ -0,0 +1,149 @@
---
name: context7-lookup
description: Retrieve up-to-date, version-specific documentation for libraries and frameworks using Context7 MCP server. Eliminates hallucinated APIs and outdated code examples.
---
# Context7 Documentation Lookup
Fetches current documentation for 68,000+ libraries to prevent hallucinated APIs and outdated code examples.
## When to Use
Auto-invoke when encountering:
- Import/require/use statements for external packages (any language)
- Error messages mentioning library names
- User specifies library: "use {library}", "implement with {framework}"
- Planning with third-party dependencies
- Dependency files present (package.json, requirements.txt, Cargo.toml, pom.xml, Gemfile, go.mod, composer.json, *.csproj, etc.)
## Protocol
**Step 1: Detect Version** (if library known)
```bash
grep_search(query="{library}", includePattern="**/package.json") # Node.js
grep_search(query="{library}", includePattern="**/requirements.txt") # Python
grep_search(query="{library}", includePattern="**/Cargo.toml") # Rust
grep_search(query="{library}", includePattern="**/pom.xml") # Java
grep_search(query="{library}", includePattern="**/Gemfile") # Ruby
grep_search(query="{library}", includePattern="**/go.mod") # Go
grep_search(query="{library}", includePattern="**/composer.json") # PHP
```
**Step 2: Resolve Library ID**
```
mcp_context7_resolve-library-id(
libraryName: "{library}",
query: "{specific_use_case}"
)
→ Returns: /owner/repo or /domain
```
**Step 3: Fetch Documentation**
```
mcp_context7_query-docs(
libraryId: "{resolved_id}",
query: "{library} {version} {specific_api_or_pattern}"
)
→ Returns: Documentation snippets with code examples
```
**Step 4: Apply & Cache**
- Use verified API signatures in implementation
- Update FINDINGS.md with: library, version, Context7 ID, date, key patterns
## Version Detection
| Ecosystem | Files | Extract From |
|-----------|-------|--------------|
| Node.js | package.json, *lock.yaml | `dependencies` / `devDependencies` |
| Python | requirements.txt, Pipfile, pyproject.toml | Version specifiers (==, >=, ~=) |
| Java | pom.xml, build.gradle* | `<version>` / version strings |
| Rust | Cargo.toml | `[dependencies]` |
| Ruby | Gemfile* | Gem versions |
| Go | go.mod | Module versions |
| PHP | composer.json | `require` |
| C#/.NET | *.csproj, packages.config | PackageReference |
| Swift | Package.swift, Podfile | Dependencies |
## Library Extraction from Imports
| Language | Import | Extract |
|----------|--------|---------|
| JS/TS | `import X from 'lodash'` | `lodash` |
| Python | `import django.core` | `django` |
| Java | `import org.springframework.*` | `spring` or `spring-boot` |
| Rust | `use tokio::runtime` | `tokio` |
| Go | `import "github.com/gin-gonic/gin"` | `gin` |
| Ruby | `require 'rails'` | `rails` |
| PHP | `use Symfony\Component` | `symfony` |
| C# | `using Microsoft.AspNetCore` | `AspNetCore` |
**Query Strategy:**
1. User-specified version → use as-is
2. Detected from files → include in query
3. No version found → use latest
4. Always format: "{library} {version} {specific_feature}"
## Best Practices
**✅ Specific queries:**
- "Django 5.0 class-based views authentication"
- "Tokio 1.35 async runtime spawning tasks"
- "Spring Boot 3.2 @RestController validation"
**❌ Vague queries:**
- "authentication"
- "async programming"
**Skip resolve if ID known:**
```
query-docs("/django/django", "Django 5.0 ORM filtering") # Direct
```
**Common ID patterns:**
- GitHub: `/{owner}/{repo}` (e.g., `/vercel/next.js`)
- Docs: `/{domain}` (e.g., `/tailwindcss.com/docs`)
## Error Handling
| Error | Action |
|-------|--------|
| 404 Not Found | Try alternative names, ask user |
| 429 Rate Limited | Wait 60s, retry (set CONTEXT7_API_KEY for higher limits) |
| Multiple matches | Present top 3, ask clarification |
| Empty results | Broader query or fallback to web search |
## Example (Multi-Language)
```python
# Python: Django REST API
Task: "Add REST views with Django"
1. grep_search("django", "requirements.txt") "django==5.0.2"
2. resolve-library-id("django", "REST API views") /django/django
3. query-docs("/django/django", "Django 5.0 class-based views REST") docs
4. Implement with verified APIs
```
```rust
// Rust: Tokio Async
Task: "Async HTTP with Tokio"
1. grep_search("tokio", "Cargo.toml") 'tokio = "1.35"'
2. query-docs("/tokio-rs/tokio", "tokio 1.35 async HTTP runtime") docs
3. Use: Runtime::new() NOT: Runtime::init()
```
```java
// Java: Spring Boot
Reviewer detecting Spring Boot 3.2 from pom.xml
1. query-docs("/spring-projects/spring-boot", "Spring Boot 3.2 @RequestMapping")
2. Flag: Use @GetMapping instead of @RequestMapping in v3.2
```
## MCP Tools
**resolve-library-id** → Search library by name, returns ID
**query-docs** → Fetch docs by ID, returns snippets
---
Use liberally to prevent hallucinated APIs. Cache results in FINDINGS.md.

View File

@@ -0,0 +1,74 @@
```skill
---
name: conventional-commit
description: Conventional commit message format with mandatory JIRA ID for all commits in the pipeline.
---
# Mobi Commit Convention
## Format
```
{commit_type}({jira}): {description}
```
- `{commit_type}` — conventional commit type (see table below)
- `{jira}` — JIRA ticket ID (e.g., `CACTUS-1234`) — **MANDATORY**
- `{description}` — imperative mood, lowercase, no trailing period
## Work Type → Commit Type Mapping
| Work Type (from prompt.yaml) | Commit Type | Example |
|------------------------------|-------------|---------|
| feature | `feat` | `feat(CACTUS-1234): add OAuth login flow` |
| fix | `fix` | `fix(PROJ-567): resolve null pointer in auth handler` |
| chore | `chore` | `chore(CACTUS-890): update dependency versions` |
For iteration-level commits where multiple changes are bundled:
```
{commit_type}({jira}): complete iteration {N} — {brief summary}
```
Example: `feat(CACTUS-1234): complete iteration 0 — implement user auth endpoints`
## Rules
1. JIRA ID is **MANDATORY** in every commit — a commit without JIRA is a CRITICAL ERROR
2. Imperative mood: "add" not "added" or "adds"
3. Total first line under 72 characters
4. No period at end of description
5. Scope field is always the JIRA ticket ID
6. Description must summarize WHAT changed, not HOW
## Multi-line Commits (Optional)
For complex iterations, use a body:
```
{commit_type}({jira}): {summary}
- {change 1}
- {change 2}
- {change 3}
```
## Pipeline Integration
After iteration acceptance (Orchestrator step O6):
```sh
git add -u && git add $(git ls-files --others --exclude-standard) && git commit -m "{commit_type}({jira}): complete iteration {N} — {summary}"
```
**Staging rules**:
- `git add -u` stages all tracked file modifications and deletions.
- `git add $(git ls-files --others --exclude-standard)` stages new untracked files respecting `.gitignore`.
- This replaces `git add -A` to avoid accidentally staging untracked artifacts outside the working tree.
- NEVER stage files outside the project root or in `prompts/` (should be in `.gitignore`).
Where:
- `{commit_type}` is derived from `type` field in `prompt.yaml`
- `{jira}` is the ticket ID
- `{N}` is the iteration number
- `{summary}` is a brief description of what was accomplished (2-5 words)
```

112
.github/skills/create-branch/SKILL.md vendored Normal file
View File

@@ -0,0 +1,112 @@
```skill
---
name: create-branch
description: Git branch naming convention and creation workflow — handles working tree state, default branch detection, and interactive cleanup before branching.
---
# Create Branch
## Just-In-Time Invocation
Use this skill as the just-in-time mutation gate before the first code-writing step.
Do not treat this skill as a mandatory startup blocker for read-only startup analysis or planning.
Run this skill after the task graph is known and before the first task-executor launch.
## Branch Naming Convention
Format: `{type}/{jira}_{slug}`
- `{type}` — work type: `feature`, `fix`, `chore`
- `{jira}` — JIRA ticket ID (e.g., `CACTUS-1234`)
- `{slug}` — 3-word snake_case descriptor
Examples:
- `feature/CACTUS-1234_add_user_authentication`
- `fix/PROJ-567_fix_login_button`
- `chore/CACTUS-890_update_dependencies`
## Default Branch Detection
Detect the default branch name:
```sh
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'
```
Fallback: check if `master` exists, else `main`. Store as `{default_branch}`.
## Git State Initialization
Run these checks BEFORE creating a branch:
### 1. Get Current State
```sh
CURRENT_BRANCH=$(git branch --show-current)
IS_DIRTY=$(git status --porcelain)
```
Collect changed paths (staged, unstaged, untracked) and classify whether the dirty tree is documentation-only:
```sh
CHANGED_FILES=$(
{
git diff --name-only
git diff --cached --name-only
git ls-files --others --exclude-standard
} | sed '/^$/d' | sort -u
)
NON_DOC_FILES=$(printf '%s\n' "$CHANGED_FILES" | grep -Ev '^(docs/|.*\.md$|docs/docs_cache_state\.yaml$)' || true)
DOCS_ONLY_DIRTY=false
if [ -n "$CHANGED_FILES" ] && [ -z "$NON_DOC_FILES" ]; then
DOCS_ONLY_DIRTY=true
fi
```
### 2. Handle State Matrix
| Current Branch | Working Tree | Action |
|---------------|--------------|--------|
| `{default_branch}` | Clean | `git pull` → create branch |
| `{default_branch}` | Dirty (docs-only) | Auto-stash docs → `git pull` → create branch → `git stash pop` |
| `{default_branch}` | Dirty (non-doc or mixed) | Ask user → handle → `git pull` → create branch |
| Other branch | Clean | `git checkout {default_branch}``git pull` → create branch |
| Other branch | Dirty (docs-only) | Auto-stash docs → `git checkout {default_branch}``git pull` → create branch → `git stash pop` |
| Other branch | Dirty (non-doc or mixed) | Ask user → handle → `git checkout {default_branch}``git pull` → create branch |
### 3. Documentation-Only Dirty Tree (Automatic Path)
If the working tree is dirty and `DOCS_ONLY_DIRTY=true`, do not prompt the user.
1. `git stash push -u -m "WIP: docs-only auto-stash before {jira}"`
2. Continue normal branch setup (`checkout {default_branch}` when needed, `git pull`, then create/switch target branch)
3. `git stash pop`
If `git stash pop` reports conflicts, stop and ask the user how to resolve the conflict before continuing.
### 3. Dirty Working Tree Options
When dirty and the changes are non-doc or mixed, present these options to the user (use `ask_questions` tool):
- **Stash changes**: `git stash push -m "WIP: auto-stash before {jira}"` — saves changes for later
- **Discard changes**: `git checkout -- . && git clean -fd` — permanently removes uncommitted changes
- **Abort**: Stop the workflow — user handles git state manually
After stashing or discarding, working tree is clean. Proceed with checkout/pull.
## Branch Creation
After git state is clean and on `{default_branch}` with latest pulled:
```sh
# Check if branch already exists locally
git branch --list "{type}/{jira}_{slug}"
# Check if branch exists on remote
git ls-remote --heads origin "{type}/{jira}_{slug}"
```
- **Does not exist**: `git checkout -b {type}/{jira}_{slug}`
- **Exists locally**: `git checkout {type}/{jira}_{slug}`
- **Exists only on remote**: `git checkout -b {type}/{jira}_{slug} origin/{type}/{jira}_{slug}`
```

601
.github/skills/deploy-app/README.md vendored Normal file
View File

@@ -0,0 +1,601 @@
# Deploy Application Skill
Automated deployment of Dockerized applications to remote servers.
## Features
**Automated Docker builds and registry pushes** to Gitea
**SSH-based remote deployment** with compose orchestration
**Automatic reverse proxy** configuration with Nginx Proxy Manager
**Subdomain routing** - Apps served as `{app-name}.{root-domain}`
**SSL certificate** generation via Let's Encrypt
**Smart .env handling** - Auto-generates secrets, infers service URLs
**Network topology** management (proxy, ollama)
**No host port publishing** - Webapps are reached through Docker network + Nginx Proxy Manager
**Organized data storage** - All volumes under `./data/`
**Health monitoring** and log inspection
**Rollback guidance** on failures
**Update-aware deployments** - Detects existing stack/proxy and performs safe in-place updates
## Quick Start
### 1. Run Setup Wizard
```bash
cd .github/skills/deploy-app
chmod +x setup-wizard.sh
./setup-wizard.sh
```
Follow the prompts to generate your deployment configuration.
### 2. Deploy Your App
```
deploy production
```
That's it! The agent will handle both first deploys and updates.
If the remote stack directory or proxy host already exists, deployment is treated as an **update** (with idempotent proxy handling and additive env/volume changes).
## Files
- **SKILL.md** - Complete skill documentation (for the agent)
- **setup-wizard.sh** - Interactive configuration generator
- **README.md** - This file (user documentation)
## What Gets Deployed
```
Remote Server: /srv/docker/stacks/my-app/
├── compose.yaml # Generated Docker Compose file
├── .env # Environment variables (if detected)
└── data/ # Persistent application data
├── <app-data> # Main application data
├── uploads/ # If app needs uploads (auto-detected)
├── cache/ # If app needs cache (auto-detected)
└── logs/ # If app needs logs (auto-detected)
```
All volumes are organized under the `data/` directory for clean separation.
## Supported Application Types
- **webapp** - Web applications with reverse proxy and SSL
- **service** - Backend services without public access
- **worker** - Background workers and queue processors
## Requirements
**Local:**
- Docker installed
- Access to workspace with Dockerfile
- Environment variable `DEPLOY_CONFIG_{TARGET}` configured
**Remote Server:**
- Docker and Docker Compose installed
- SSH access configured
- Docker networks created (`proxy`, optionally `ollama`)
**Services:**
- Gitea instance with container registry enabled
- Nginx Proxy Manager (for webapps)
## Configuration
See configuration examples below for different deployment scenarios.
### Production Server
```bash
export DEPLOY_CONFIG_PRODUCTION='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "production-apps",
"ssh_host": "prod-server",
"stacks_dir": "/srv/docker/stacks",
"domain": "sal.giize.com",
"npm_url": "https://proxy.mycompany.com",
"npm_username": "admin@mycompany.com",
"npm_password": "npm-admin-password"
}'
# All app-specific settings (name, port, env vars, volumes, type)
# are auto-detected from your codebase!
# App will be served at: {app-name}.sal.giize.com
```
### Staging Server
```bash
export DEPLOY_CONFIG_STAGING='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "staging-apps",
"ssh_host": "staging-server",
"stacks_dir": "/home/deploy/stacks",
"domain": "staging.sal.giize.com",
"npm_url": "https://proxy-staging.mycompany.com",
"npm_username": "admin@mycompany.com",
"npm_password": "npm-admin-password"
}'
```
### AI Service with LLM
```bash
export DEPLOY_CONFIG_AI_SERVICE='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "ai-services",
"ssh_host": "ai-server",
"stacks_dir": "/srv/docker/stacks",
"domain": "ai.sal.giize.com",
"npm_url": "https://proxy.mycompany.com",
"npm_username": "admin@mycompany.com",
"npm_password": "npm-admin-password",
"proxy_network": "proxy",
"ollama_network": "ollama",
"ollama_container_name": "ollama"
}'
# Network names are customizable
# App will auto-connect to Ollama at: http://ollama:11434
# (or your custom ollama_container_name:11434)
```
### Background Worker (No Web Interface)
For services without a web interface, omit the NPM fields:
```bash
export DEPLOY_CONFIG_WORKER='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "workers",
"ssh_host": "worker-server",
"stacks_dir": "/srv/docker/stacks"
}'
```
### Configuration Fields
**Required for All Deployments:**
- `gitea_registry_url` - Your Gitea instance domain (without https://)
- `gitea_username` - Gitea username
- `gitea_token` - Access token with package permissions
- `gitea_namespace` - Organization or username
- `ssh_host` - SSH alias from ~/.ssh/config
- `stacks_dir` - Directory on remote server for stacks
**Required for Webapps:**
- `domain` - Root domain (e.g., `sal.giize.com`). App will be served at `{app-name}.{domain}`
- `npm_url` - Nginx Proxy Manager URL
- `npm_username` - NPM admin email
- `npm_password` - NPM admin password
**Optional Network Configuration:**
- `proxy_network` - Docker network name for reverse proxy (default: `proxy`)
- `ollama_network` - Docker network name for Ollama LLM (default: `ollama`)
- `ollama_container_name` - Ollama container name for URL generation (default: `ollama`)
- Apps requiring LLM will auto-set `OLLAMA_BASE_URL=http://{ollama_container_name}:11434`
**Auto-Detected from Codebase:**
- **App name** - From package.json, Cargo.toml, pyproject.toml, go.mod, or directory name
- **Port** - From Dockerfile EXPOSE, .env PORT, code patterns, or defaults to 3000
- Used as internal container port for proxy upstream (not published on host by default)
- **App type** - webapp/service/worker based on dependencies and keywords
- **Environment variables** - Smart .env processing:
- 🔐 Auto-generates secrets (JWT_SECRET, APP_KEY, etc.) using secure random values
- 🔌 Infers service URLs (DATABASE_URL, REDIS_URL) from dependencies
- 📋 Copies simple values (NODE_ENV, LOG_LEVEL) as-is
- ❓ Asks for external API keys and credentials
- **Volume mounts** - From README, Dockerfile VOLUME, common directories (bound under ./data/)
- **LLM requirements** - From dependencies (openai, ollama, llama)
All detected settings are confirmed with you before deployment.
**Volume Binding:** All volumes are automatically bound under `./data/` on the host.
Example: If app needs uploads, it creates `./data/uploads:/app/uploads`
**Domain Handling:** The `domain` field is the root domain. Your app is served as a subdomain.
Example: `domain: "sal.giize.com"` + app name `my-api` → URL: `my-api.sal.giize.com`
**DNS Requirements:** Ensure you have a wildcard DNS record or individual A/CNAME records:
- Wildcard: `*.sal.giize.com``your-server-ip`
- Individual: `my-api.sal.giize.com``your-server-ip`
## Deployment Flow
1. **Parse target** - Extract deployment target name
2. **Auto-detect app** - Scan codebase for app name, port, type, env vars, volumes, LLM usage
3. **Process .env** - Generate secrets, infer service URLs, identify user inputs
4. **Load config** - Get server/credential configuration
5. **Detect mode** - Classify deployment as `new` or `update` using existing remote stack/proxy state
6. **Confirm detection** - Show detected config and mode, ask for approval
7. **Confirm environment** - Review .env processing and provide any needed values
8. **Validate** - Check Dockerfile exists
9. **Build** - Test build locally
10. **Push** - Tag and push to Gitea registry
11. **Transfer** - SSH to remote server
12. **Setup** - Create/update directories, deploy merged `.env` and compose files
13. **Deploy** - `docker compose down`, `docker compose pull`, `docker compose up -d`
14. **Configure** - Create or update reverse proxy (webapps)
- Upstream target uses Docker service + internal port (for example `my-app:3000`)
15. **Verify** - Inspect logs and test application availability
## Key Features Explained
### Subdomain Routing
Apps are automatically served as subdomains of your root domain:
- Config: `"domain": "sal.giize.com"`
- App name: `my-api` (auto-detected)
- **Result:** App accessible at `my-api.sal.giize.com`
**DNS Setup:** Configure wildcard DNS: `*.sal.giize.com` → your server IP
### Volume Organization
All persistent data is organized under `./data/`:
```
data/
├── <app-data>/ # Main app data (always created)
├── uploads/ # Auto-detected from codebase
├── cache/ # Auto-detected from codebase
└── logs/ # Auto-detected from codebase
```
Docker compose mounts: `./data/uploads:/app/uploads`
### Auto-Detection
The skill scans your codebase to detect:
- App name from package.json, Cargo.toml, pyproject.toml, go.mod, or directory
- Container port from Dockerfile EXPOSE, .env, or code patterns
- App type (webapp/service/worker) from dependencies
- Environment variables from .env files
- Volume requirements from README, Dockerfile, common patterns
- LLM dependencies (openai, ollama)
All detected settings shown to you for confirmation before deployment.
### Smart .env Handling
When a `.env`, `.env.example`, or `.env.template` file is detected, the skill intelligently processes it:
**🔐 Auto-Generated Secrets**
Variables like `JWT_SECRET`, `APP_KEY`, `SESSION_SECRET` are automatically generated using secure random values:
```bash
JWT_SECRET=a1b2c3d4e5f6... # 32-byte hex string
```
**🔌 Inferred Service URLs**
Database and service connections are constructed from container names based on detected dependencies:
```bash
DATABASE_URL=postgresql://my-app-db:5432/my-app
REDIS_URL=redis://my-app-redis:6379
MONGODB_URI=mongodb://my-app-mongo:27017/my-app
```
**📋 Copied Values**
Simple configuration values are preserved:
```bash
NODE_ENV=production
LOG_LEVEL=info
TZ=UTC
```
**❓ User Input**
External API keys and credentials are identified and you're prompted for values:
```bash
STRIPE_SECRET_KEY=sk_live_... # You'll be asked to provide this
SENDGRID_API_KEY=SG... # You'll be asked to provide this
```
**Confirmation**
Before deployment, you'll see:
- What was generated (with secure random values)
- What was inferred (service URLs)
- What was copied (simple configs)
- What needs your input (API keys)
You can proceed as-is or provide specific values. The complete `.env` file is deployed to the server with secure permissions (chmod 600).
### Update-Safe Drift Handling
When deployment mode is `update`, the skill applies additive and non-destructive changes:
- **Environment merge:** existing remote `.env` keys are preserved; newly required keys are appended.
- **Required secrets:** unresolved required placeholders still block deployment until provided.
- **Volume drift:** new host directories are created as needed; existing volume paths are kept intact.
- **Compose stability:** service naming remains stable for idempotent updates.
This minimizes downtime and avoids accidental configuration loss during redeployments.
## Quick Setup Guide
### 1. Create Gitea Token
```bash
# Login to Gitea
# Navigate to Settings → Applications → Generate New Token
# Select scopes: write:package, read:package
# Copy the generated token
```
### 2. Configure SSH
```bash
# Edit ~/.ssh/config
cat >> ~/.ssh/config << 'EOF'
Host prod-server
HostName 192.168.1.100
User deploy
IdentityFile ~/.ssh/deploy_key
StrictHostKeyChecking accept-new
EOF
# Test connection
ssh prod-server "echo 'Connection successful'"
```
### 3. Prepare Remote Server
```bash
# SSH to server
ssh prod-server
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Create networks (run once per server)
# Use your configured network names (defaults shown below)
docker network create proxy # or your proxy_network value
docker network create ollama # or your ollama_network value (for LLM apps)
# Create stacks directory
sudo mkdir -p /srv/docker/stacks
sudo chown $USER:$USER /srv/docker/stacks
```
### 4. Set Environment Variable
**Linux/macOS** (add to ~/.bashrc or ~/.zshrc):
```bash
export DEPLOY_CONFIG_PRODUCTION='{ ... your JSON config ... }'
```
**Windows PowerShell**:
```powershell
$config = @'
{ ... your JSON config ... }
'@
[System.Environment]::SetEnvironmentVariable('DEPLOY_CONFIG_PRODUCTION', $config, 'User')
```
### 5. Deploy
Just tell the agent:
```
deploy production
```
## Security
- Credentials stored in environment variables only
- SSH key-based authentication
- Secrets never logged to output
- HTTPS with Let's Encrypt certificates
- Docker registry authentication
## Troubleshooting
### Quick Diagnostics
```bash
# Check environment variable
echo $DEPLOY_CONFIG_PRODUCTION
# Test SSH
ssh prod-server "docker ps"
# Test Gitea registry
docker login gitea.example.com -u username
# Check remote deployment
ssh prod-server "cd /srv/docker/stacks/my-app && docker compose logs"
```
### Common Issues
**"DEPLOY_CONFIG_{TARGET} not found"**
- Make sure you've exported the environment variable
- Restart your terminal/VS Code after setting it
- Check spelling and capitalization
**"Cannot connect to {ssh_host}"**
- Verify SSH config: `cat ~/.ssh/config`
- Test connection: `ssh {ssh_host} "echo test"`
- Check SSH key permissions: `chmod 600 ~/.ssh/deploy_key`
**"Authentication failed" (Gitea)**
- Regenerate token with correct permissions
- Ensure token hasn't expired
- Verify username is correct
**"Docker login failed"**
- Check gitea_registry_url doesn't have protocol (no https://)
- Verify token has package write permission
- Try manual login: `echo "TOKEN" | docker login gitea.example.com -u user --password-stdin`
**Container fails to start**
- Check logs: `ssh server "cd /srv/docker/stacks/app && docker compose logs"`
- Verify environment variables are correct
- Check port conflicts on server
- Ensure image pulled successfully
**Environment variables not working**
- Check .env file exists: `ssh server "cat /srv/docker/stacks/app/.env"`
- Verify .env file permissions: `ssh server "ls -la /srv/docker/stacks/app/.env"` (should be 600)
- Ensure compose.yaml has `env_file: [.env]` directive
- Test variable loading: `ssh server "cd /srv/docker/stacks/app && docker compose config"` (shows resolved env vars)
- Restart containers after .env changes: `ssh server "cd /srv/docker/stacks/app && docker compose restart"`
**Missing or incorrect service URLs**
- Verify service containers are running in same network
- Check container names match .env URLs (e.g., `my-app-db` for DATABASE_URL)
- Test connectivity: `ssh server "docker exec my-app ping my-app-db"`
- Review auto-generated URLs in deployment confirmation step
**NPM SSL certificate fails**
- Verify domain DNS points to your server
- Check ports 80 and 443 are open
- Ensure domain is publicly accessible
- Check NPM can reach Let's Encrypt servers
## API Documentation
### Gitea Container Registry
Gitea uses an OCI-compliant container registry. No special API calls needed - just standard Docker commands:
```bash
docker login {gitea_url}
docker tag image:tag {gitea_url}/{namespace}/{name}:tag
docker push {gitea_url}/{namespace}/{name}:tag
```
Images are automatically visible in Gitea under Packages.
### Nginx Proxy Manager API
Main endpoints used:
- `POST /api/tokens` - Authentication
- `POST /api/nginx/proxy-hosts` - Create reverse proxy
- `POST /api/nginx/certificates` - Request SSL cert
- `PUT /api/nginx/proxy-hosts/{id}` - Update with SSL
## Examples
### Deploy to Production
```
deploy production
```
If `production` already exists remotely, this runs as an update rollout.
### Deploy to Staging
```
deploy staging
```
If the stack already exists, the skill updates it in place.
### Deploy AI Service with LLM
```
deploy ai-service
```
For existing deployments, proxy and stack are updated idempotently.
## Advanced Usage
### Modifying Environment Variables After Deployment
If you need to update environment variables after deployment:
**Option 1: Edit .env on server**
```bash
# SSH to server and edit
ssh prod-server
cd /srv/docker/stacks/my-app
nano .env
# Restart containers to apply changes
docker compose restart
```
**Option 2: Redeploy with updated .env**
Update your local `.env` file and redeploy - the skill will process it again in update mode and merge new required keys without deleting existing ones.
### Adding Service Containers
If your app needs a database or other services, create them in the same stack:
```bash
ssh prod-server
cd /srv/docker/stacks/my-app
nano compose.yaml
```
Add service definitions:
```yaml
services:
my-app:
# ... existing config
depends_on:
- db
- redis
db:
image: postgres:16
container_name: my-app-db
environment:
POSTGRES_DB: my-app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./data/postgres:/var/lib/postgresql/data
redis:
image: redis:7
container_name: my-app-redis
volumes:
- ./data/redis:/data
```
The DATABASE_URL and REDIS_URL will already be correctly set by the auto-detection!
### Additional Volumes
```json
{
"volumes": [
"./uploads:/app/uploads",
"./cache:/app/cache:ro"
]
}
```
### Multiple Targets
Create multiple configs:
```bash
export DEPLOY_CONFIG_PRODUCTION='{ ... }'
export DEPLOY_CONFIG_STAGING='{ ... }'
export DEPLOY_CONFIG_DEVELOPMENT='{ ... }'
```
Deploy to any target:
```
deploy development
deploy staging
deploy production
```
## License
Part of the Copilot Agents skill library.

177
.github/skills/deploy-app/SKILL.md vendored Normal file
View File

@@ -0,0 +1,177 @@
---
name: deploy-app
description: Automated deployment of Dockerized applications to remote servers with Gitea registry, Docker Compose orchestration, and Nginx Proxy Manager integration.
---
# Deploy Application
Lean-mode deployment contract for token-efficient execution. Use this file for runtime behavior. For deep examples and full walkthroughs, reference [README.md](./README.md).
## Prerequisites
- `DEPLOY_CONFIG_{TARGET}` env var exists and is valid JSON
- SSH host alias works from `~/.ssh/config`
- `Dockerfile` exists in workspace root
- Docker available locally and on target server
## Deployment Steps
### 1. Parse target
Extract `{TARGET}` from user request (for example: `deploy production``PRODUCTION`).
### 2. Detect app metadata (minimal set)
Detect and store:
- `{app_name}`: from `package.json` / `Cargo.toml` / `pyproject.toml` / `go.mod` / directory fallback
- `{port}`: from `EXPOSE`, `.env PORT`, or framework patterns; fallback `3000` (container internal port)
- `{app_type}`: `webapp`, `service`, or `worker`
- `{requires_llm}`: true if `openai|ollama|llama` detected
- `{detected_volumes}`: bind all host paths under `./data/` (including secrets paths)
If `.env*` exists (excluding `.env.local`), process variables into:
- generated secrets (`*SECRET*`, `*KEY`, `*TOKEN`)
- inferred service URLs (`DATABASE_URL`, `REDIS_URL`, etc.)
- copied simple values (`NODE_ENV`, `PORT`, `LOG_LEVEL`, ...)
- user-required values (placeholders/external API keys)
### 3. Load and validate deploy config
Read `DEPLOY_CONFIG_{TARGET}` and validate required fields:
- `gitea_registry_url`, `gitea_username`, `gitea_token`, `gitea_namespace`
- `ssh_host`, `stacks_dir`
If missing/invalid: STOP with explicit error.
### 4. Confirm detected configuration
Use `ask_questions` with detected summary (app, port, type, env, volumes, URL if webapp, deploy mode).
If user declines, STOP and request adjustments.
If user-input env values are required, collect them before continuing.
### 5. Validate and build locally
Verify Dockerfile exists, then run:
- `docker build -t {app_name}:test-build .`
If build fails: STOP and report error output.
### 6. Push image to registry
1. Login with `--password-stdin`
2. Tag as `latest` and `{timestamp}` (`YYYYMMDD-HHMMSS`)
3. Push both tags
If auth/network/permission fails: STOP with targeted troubleshooting.
### 7. Detect deploy mode (new vs update)
On remote host and in NPM API (when configured), detect:
- stack path exists: `{stacks_dir}/{app_name}`
- compose file exists: `{stacks_dir}/{app_name}/compose.yaml`
- proxy host exists for `{app_name}.{domain}` (webapp only)
Classification:
- `new`: none of the above exist
- `update`: stack path or proxy host already exists
If only one side exists (for example proxy exists but stack does not, or inverse), continue in cautious update mode and include this mismatch in confirmation.
### 8. Prepare remote stack
1. Verify SSH connectivity
2. Create `{stacks_dir}/{app_name}/data` and required subdirectories
3. Process `.env` with update-safe merge strategy:
- if update and remote `.env` exists, preserve existing keys/values
- append newly required keys not present remotely
- never delete existing keys automatically
- block on unresolved required user-input secrets/placeholders
4. Apply secure permissions to remote `.env` (`chmod 600`)
5. Handle volume drift safely:
- create newly required host directories
- enforce all volume and secrets host paths as children of `./data/`
- keep existing volume paths intact
- never remove existing volume mounts automatically
### 9. Generate and upload compose.yaml
Compose requirements:
- image: `{gitea_registry_url}/{gitea_namespace}/{app_name}:latest`
- restart: `unless-stopped`
- all volumes and secrets host paths under `./data`
- do NOT publish ports on host (no `ports:` mapping by default)
- use external proxy network for `webapp`
- add ollama network and `OLLAMA_BASE_URL` when `{requires_llm}=true`
Update behavior for compose generation:
- keep service naming stable for idempotent updates
- preserve existing mounts/networks unless user explicitly confirms removal
- if update and env + volume definitions are unchanged, do NOT rewrite `compose.yaml`
- if update requires compose rewrite, fetch remote `compose.yaml` and use it as template baseline before applying required deltas
Upload compose file to `{stacks_dir}/{app_name}/compose.yaml`.
### 10. Deploy on server
On remote host:
1. `docker login` to registry
2. `docker compose down`
3. `docker compose pull`
4. `docker compose up -d`
4. Inspect logs (`--tail=50`) after short wait
If container exits or logs show startup failure: STOP and report logs.
### 11. Configure reverse proxy (webapp only)
If `{app_type}=webapp` and NPM config exists:
1. Authenticate to NPM API
2. Check if proxy host for `{app_name}.{domain}` already exists
3. Set upstream target to Docker service name + internal container port (`{app_name}:{port}`) on shared proxy network
4. If existing: update upstream target and SSL settings (idempotent update)
5. If not existing: create proxy host, request certificate, then enable SSL
If SSL creation fails: WARN and continue HTTP-only.
### 12. Verify deployment
- Webapp: `curl -I https://{app_name}.{domain}` and expect `2xx/3xx`
- Non-webapp: verify running container and health/log readiness
Return structured success summary (image, host, location, URL/container).
## Error Recovery
On failure:
1. Show concrete error
2. Provide immediate remediation steps
3. STOP (no implicit continuation)
4. Offer rollback option using ONLY non-destructive actions:
- run `docker compose down` in the app stack directory
- rollback changes in Nginx Proxy Manager (remove/disable created proxy host entries for this deploy)
## Forbidden Operations
Safety policy for this skill is strict:
- NEVER execute deletion commands on local or remote systems.
- NEVER run any `rm`-family or delete operations, including recursive deletes.
- NEVER use destructive cleanup commands that remove files, directories, volumes, or containers.
Allowed remediation is limited to:
- `docker compose down`
- rollback changes in Nginx Proxy Manager
If a remediation path would require deletion, STOP and report manual follow-up steps without executing destructive commands.
## Security
- Never print secrets in logs
- Use `--password-stdin` for all docker logins
- Quote shell variables
- Validate all required inputs before executing remote operations
## Notes
- Keep runtime behavior minimal and deterministic.
- For advanced examples, edge-case recipes, and setup wizard usage, see [README.md](./README.md).

View File

@@ -0,0 +1,139 @@
#!/bin/bash
# Quick setup script for deployment configuration
set -e
echo "==================================="
echo "Deployment Skill Setup Wizard"
echo "==================================="
echo ""
# Function to read input with default
read_input() {
local prompt="$1"
local default="$2"
local var_name="$3"
if [ -n "$default" ]; then
read -p "$prompt [$default]: " input
eval "$var_name=\"${input:-$default}\""
else
read -p "$prompt: " input
eval "$var_name=\"$input\""
fi
}
# Target name
read_input "Enter deployment target name (e.g., PRODUCTION, STAGING)" "" TARGET
TARGET=$(echo "$TARGET" | tr '[:lower:]' '[:upper:]')
echo ""
echo "--- Gitea Configuration ---"
read_input "Gitea registry URL (without https://)" "gitea.example.com" GITEA_URL
read_input "Gitea username" "" GITEA_USER
echo "Gitea token (will be hidden): "
read -s GITEA_TOKEN
echo ""
read_input "Gitea namespace (org or username)" "$GITEA_USER" GITEA_NAMESPACE
echo ""
echo "--- Remote Server Configuration ---"
read_input "SSH host alias (from ~/.ssh/config)" "" SSH_HOST
read_input "Stacks directory on remote server" "/srv/docker/stacks" STACKS_DIR
echo ""
echo "--- Webapp Configuration (optional, press Enter to skip) ---"
read_input "Root domain (e.g., sal.giize.com - app served as subdomain)" "" DOMAIN
# Conditional NPM fields
if [ -n "$DOMAIN" ]; then
read_input "Nginx Proxy Manager URL" "https://npm.example.com" NPM_URL
read_input "NPM admin email" "admin@example.com" NPM_USER
echo "NPM password (will be hidden): "
read -s NPM_PASS
echo ""
fi
echo ""
echo "--- Network Configuration (optional, press Enter for defaults) ---"
read_input "Proxy network name" "proxy" PROXY_NETWORK
read_input "Ollama network name (for AI/LLM apps)" "ollama" OLLAMA_NETWORK
read_input "Ollama container name (for deriving connection URL)" "ollama" OLLAMA_CONTAINER
# Generate JSON config
if [ -n "$DOMAIN" ]; then
JSON_CONFIG=$(cat <<EOF
{
"gitea_registry_url": "$GITEA_URL",
"gitea_username": "$GITEA_USER",
"gitea_token": "$GITEA_TOKEN",
"gitea_namespace": "$GITEA_NAMESPACE",
"ssh_host": "$SSH_HOST",
"stacks_dir": "$STACKS_DIR",
"domain": "$DOMAIN",
"npm_url": "$NPM_URL",
"npm_username": "$NPM_USER",
"npm_password": "$NPM_PASS",
"proxy_network": "$PROXY_NETWORK",
"ollama_network": "$OLLAMA_NETWORK",
"ollama_container_name": "$OLLAMA_CONTAINER"
}
EOF
)
else
JSON_CONFIG=$(cat <<EOF
{
"gitea_registry_url": "$GITEA_URL",
"gitea_username": "$GITEA_USER",
"gitea_token": "$GITEA_TOKEN",
"gitea_namespace": "$GITEA_NAMESPACE",
"ssh_host": "$SSH_HOST",
"stacks_dir": "$STACKS_DIR",
"proxy_network": "$PROXY_NETWORK",
"ollama_network": "$OLLAMA_NETWORK",
"ollama_container_name": "$OLLAMA_CONTAINER"
}
EOF
)
fi
echo ""
echo "==================================="
echo "Configuration generated!"
echo "==================================="
echo ""
echo "Add this to your shell configuration file (~/.bashrc, ~/.zshrc, etc.):"
echo ""
echo "export DEPLOY_CONFIG_$TARGET='$JSON_CONFIG'"
echo ""
echo "Then restart your terminal or run:"
echo "source ~/.bashrc # or ~/.zshrc"
echo ""
# Optionally write to file
read -p "Save to file? (y/n): " SAVE_FILE
if [ "$SAVE_FILE" = "y" ] || [ "$SAVE_FILE" = "Y" ]; then
CONFIG_FILE="deploy_config_${TARGET}.env"
echo "export DEPLOY_CONFIG_$TARGET='$JSON_CONFIG'" > "$CONFIG_FILE"
echo ""
echo "✓ Saved to $CONFIG_FILE"
echo "Load it with: source $CONFIG_FILE"
fi
echo ""
echo "==================================="
echo "Next Steps:"
echo "==================================="
echo ""
echo "1. Set the environment variable (see above)"
echo "2. Verify SSH access: ssh $SSH_HOST \"echo 'Test OK'\""
echo "3. Create Docker networks on remote server (run once per server):"
echo " ssh $SSH_HOST \"docker network create $PROXY_NETWORK\""
if [ -n "$OLLAMA_NETWORK" ]; then
echo " ssh $SSH_HOST \"docker network create $OLLAMA_NETWORK\" # For AI/LLM apps"
fi
echo "4. Navigate to your application directory"
echo "5. Tell the agent: 'deploy $TARGET'"
echo ""
echo "The agent will auto-detect your app's configuration and ask for confirmation!"
echo ""

183
.github/skills/merge-branch/SKILL.md vendored Normal file
View File

@@ -0,0 +1,183 @@
```skill
---
name: merge-branch
description: Git branch merging workflow — merges current feature branch into the default branch (main/master) with conflict detection, working tree validation, and safety checks.
---
# Merge Branch
## Overview
This skill merges the current feature branch into the detected default branch (main or master).
**Prerequisites:**
- Working in a git repository
- On a feature branch (not already on default branch)
- Have commits ready to merge
**Outcome:**
- Feature branch merged into default branch
- Default branch updated with latest changes
- Ready to push merged changes to remote
## Default Branch Detection
Detect the default branch name:
```sh
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'
```
Fallback: check if `master` exists, else `main`. Store as `{default_branch}`.
## Pre-Merge Validation
Run these checks BEFORE merging:
### 1. Get Current Branch
```sh
CURRENT_BRANCH=$(git branch --show-current)
```
### 2. Verify Not on Default Branch
```sh
if [ "$CURRENT_BRANCH" = "{default_branch}" ]; then
echo "ERROR: Already on default branch. Cannot merge into self."
exit 1
fi
```
### 3. Check Working Tree State
```sh
IS_DIRTY=$(git status --porcelain)
```
If `IS_DIRTY` is not empty, working tree is dirty. Handle before merging.
## Dirty Working Tree Options
When dirty, present these options to the user (use `ask_questions` tool):
- **Commit changes first**: `git add -A && git commit -m "message"` — commits all changes before merging
- **Stash changes**: `git stash push -m "WIP: auto-stash before merge"` — saves changes for later
- **Discard changes**: `git checkout -- . && git clean -fd` — permanently removes uncommitted changes
- **Abort**: Stop the workflow — user handles git state manually
After committing, stashing, or discarding, working tree is clean. Proceed with merge.
## Merge Execution
After working tree is clean:
### 1. Switch to Default Branch
```sh
git checkout {default_branch}
```
### 2. Pull Latest Changes
```sh
git pull
```
### 3. Execute Merge
```sh
git merge $CURRENT_BRANCH
```
### 4. Handle Merge Result
Check exit code:
- **Exit 0** (success): Merge completed successfully
```
✓ Merged $CURRENT_BRANCH into {default_branch}
Next steps:
- Review merged changes: git log
- Push to remote: git push
- Delete feature branch if done: git branch -d $CURRENT_BRANCH
```
- **Non-zero exit** (conflict): Merge conflict detected
```sh
git merge --abort
echo "ERROR: Merge conflict detected. Merge aborted."
echo "Resolve conflicts manually and retry merge."
exit 1
```
## Error Handling
| Scenario | Error Message | Exit Code |
|----------|---------------|-----------|
| Already on default branch | `ERROR: Already on default branch. Cannot merge into self.` | 1 |
| Dirty working tree (user aborts) | `ERROR: Working tree is dirty. Merge aborted.` | 1 |
| Merge conflict | `ERROR: Merge conflict detected. Merge aborted.` | 1 |
| Default branch detection fails | `ERROR: Could not detect default branch.` | 1 |
All errors exit with non-zero status to prevent downstream issues.
## Usage Example
Complete workflow demonstrating all steps:
```sh
#!/bin/bash
set -e
# Step 1: Detect default branch
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
if [ -z "$DEFAULT_BRANCH" ]; then
if git show-ref --verify --quiet refs/heads/master; then
DEFAULT_BRANCH="master"
else
DEFAULT_BRANCH="main"
fi
fi
# Step 2: Get current branch
CURRENT_BRANCH=$(git branch --show-current)
# Step 3: Verify not on default branch
if [ "$CURRENT_BRANCH" = "$DEFAULT_BRANCH" ]; then
echo "ERROR: Already on default branch. Cannot merge into self."
exit 1
fi
# Step 4: Check working tree state
IS_DIRTY=$(git status --porcelain)
if [ -n "$IS_DIRTY" ]; then
echo "WARNING: Working tree is dirty."
echo "Options: (c)ommit, (s)tash, (d)iscard, (a)bort"
read -p "Choose: " choice
case $choice in
c) git add -A && git commit -m "Changes before merge" ;;
s) git stash push -m "WIP: auto-stash before merge" ;;
d) git checkout -- . && git clean -fd ;;
a) echo "Merge aborted."; exit 1 ;;
*) echo "Invalid choice. Aborting."; exit 1 ;;
esac
fi
# Step 5: Switch to default branch
git checkout $DEFAULT_BRANCH
# Step 6: Pull latest changes
git pull
# Step 7: Execute merge
if git merge $CURRENT_BRANCH; then
echo "✓ Merged $CURRENT_BRANCH into $DEFAULT_BRANCH"
echo "Next steps:"
echo "- Review merged changes: git log"
echo "- Push to remote: git push"
echo "- Delete feature branch if done: git branch -d $CURRENT_BRANCH"
else
echo "ERROR: Merge conflict detected. Aborting merge."
git merge --abort
exit 1
fi
```

View File

@@ -0,0 +1,36 @@
# Simplify Code Appendix
Extended examples and pattern notes live here to keep `SKILL.md` lean.
## Pattern Details
- Extract Method: split large functions into named units.
- Guard Clauses: replace deep nesting with early returns.
- Decompose Conditional: extract complex conditions and branches.
- Replace Magic Numbers: promote literals to named constants.
- Rename for Clarity: explicit, searchable, domain terms.
- Remove Dead Code: delete commented or unreachable code.
- Parameter Object: group related arguments when signatures are too long.
## Language-Specific Reminders
- JavaScript/TypeScript: prefer `const`, pure helpers, and array transforms.
- Python: prefer clear names, type hints where useful, and small functions.
- Java/C#: prefer focused classes and guard clauses.
- Go: keep functions small, explicit error paths.
## Anti-Patterns
- Large-bang rewrites
- Behavioral changes without explicit requirement
- Premature abstractions
- Clever code at cost of readability
## Suggested Validation Loop
1. Build/test baseline
2. Apply one simplification pass
3. Re-run build/tests
4. Repeat
If a pass introduces failures, stop and document in `simplification-report.md`.

103
.github/skills/simplify-code/SKILL.md vendored Normal file
View File

@@ -0,0 +1,103 @@
---
name: simplify-code
description: Systematic code simplification using proven refactoring patterns. Apply incrementally with tests at each step.
---
# Simplify Code
Lean-mode simplification contract optimized for low token usage.
Detailed pattern catalog and examples are in [APPENDIX.md](./APPENDIX.md).
## Execution Workflow
### 1. Setup
- Detect build and test commands from project files.
- If missing, ask user once and store commands.
- Build candidate file list from git-visible files only:
- prefer `git ls-files` for tracked + unignored files
- include untracked-but-unignored files when relevant (`git ls-files --others --exclude-standard`)
- NEVER simplify files ignored by `.gitignore` (or `.git/info/exclude` / global gitignore)
- if workspace is not a git repo, ask user for include paths before modifying files
### 2. Baseline Validation
- Run build; if failing, STOP.
- Run tests; if failing, STOP.
### 3. Prepare Tooling
- Detect and run formatter/linter appropriate for the language.
- Keep edits style-compliant before simplification passes.
### 4. Simplification Passes
- Prioritize largest or most complex files first (within the git-visible candidate list).
- Apply one refactoring pattern at a time.
- Keep behavior unchanged.
### 5. Verify
- Re-run build and tests after each pass or grouped pass.
- If failures appear, go to failure handling.
### 6. Failure Handling
- Generate `simplification-report.md` with:
- files touched
- patterns applied
- failures and diagnostics
- rollback guidance
## Detection Triggers
Target code that shows one or more of:
- long methods / large classes
- nested or complex conditionals
- duplicate logic
- magic numbers
- ambiguous naming
- long parameter lists
- dead code / commented code
- impure stateful helpers
## Core Patterns
Apply these patterns first:
1. Extract Method
2. Guard Clauses
3. Decompose Conditional
4. Replace Magic Number with Constant
5. Rename for clarity
6. Remove dead/commented code
7. Introduce parameter object when argument lists are large
Prefer pure functions and immutability where idiomatic for the language.
## Constraints
- Avoid large-bang refactors.
- Keep each change small and reversible.
- Do not change public behavior unless explicitly requested.
- Do not introduce speculative abstractions.
- Do not read, modify, or propose edits for files ignored by git ignore rules.
## Metrics (Targets)
- Cyclomatic complexity `< 10`
- Method length `< 30` lines
- Nesting depth `< 4`
- Parameter count `< 4`
- Class length `< 300` lines
## Language Notes
- Use ecosystem formatters/linters (Prettier/ESLint, Black/isort, gofmt, etc.).
- Follow project-native naming conventions.
- Prefer readability over cleverness.
## Quick Mapping
- Long method → Extract Method
- Nested conditionals → Guard Clauses + Decompose Conditional
- Duplicate code → Extract shared helper
- Magic numbers → Named constants
- Dead code → Remove
## Reference
For expanded examples and anti-pattern details, see [APPENDIX.md](./APPENDIX.md).

29
.github/skills/timestamp/SKILL.md vendored Normal file
View File

@@ -0,0 +1,29 @@
```skill
---
name: timestamp
description: OS-agnostic ISO8601 timestamp generation for YAML files, logging, and any agent that needs the current time.
---
# Timestamp
## Detect OS
Check the user's OS from the environment info provided in the system prompt. Look for:
- `Windows` → use PowerShell
- `macOS` or `Linux` → use shell
## Get Current ISO8601 Timestamp
Run in terminal and capture output:
| OS | Command |
|----|---------|
| Windows | `Get-Date -Format "yyyy-MM-ddTHH:mm:ss.fffZ"` |
| macOS / Linux | `date -u +"%Y-%m-%dT%H:%M:%S.000Z"` |
## Usage
Wherever `{ISO8601}` appears in agent instructions, substitute with the actual output of the command above.
For multiple timestamps in quick succession (e.g., populating a YAML file), run the command once and reuse the value within the same step.
```