feat(SCOPONE-0010): vendor agent assets and clean docs
This commit is contained in:
1
.github/skills
vendored
1
.github/skills
vendored
@@ -1 +0,0 @@
|
||||
/home/moze/Sources/copilot-agents/.github/skills
|
||||
71
.github/skills/agent-conventions/SKILL.md
vendored
Normal file
71
.github/skills/agent-conventions/SKILL.md
vendored
Normal 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
85
.github/skills/append-log/SKILL.md
vendored
Normal 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
112
.github/skills/content-hash/SKILL.md
vendored
Normal 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.
|
||||
43
.github/skills/context7-lookup/README.md
vendored
Normal file
43
.github/skills/context7-lookup/README.md
vendored
Normal 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
149
.github/skills/context7-lookup/SKILL.md
vendored
Normal 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.
|
||||
74
.github/skills/conventional-commit/SKILL.md
vendored
Normal file
74
.github/skills/conventional-commit/SKILL.md
vendored
Normal 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
112
.github/skills/create-branch/SKILL.md
vendored
Normal 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
601
.github/skills/deploy-app/README.md
vendored
Normal 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
177
.github/skills/deploy-app/SKILL.md
vendored
Normal 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).
|
||||
139
.github/skills/deploy-app/setup-wizard.sh
vendored
Normal file
139
.github/skills/deploy-app/setup-wizard.sh
vendored
Normal 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
183
.github/skills/merge-branch/SKILL.md
vendored
Normal 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
|
||||
```
|
||||
36
.github/skills/simplify-code/APPENDIX.md
vendored
Normal file
36
.github/skills/simplify-code/APPENDIX.md
vendored
Normal 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
103
.github/skills/simplify-code/SKILL.md
vendored
Normal 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
29
.github/skills/timestamp/SKILL.md
vendored
Normal 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.
|
||||
```
|
||||
Reference in New Issue
Block a user