Compare commits
10
Commits
4c0285bf98
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffcd3ba8e3 | ||
|
|
95c77c694f | ||
|
|
530eb111a0 | ||
|
|
bc9a88328d | ||
|
|
288939bb5a | ||
|
|
8bcccfc022 | ||
|
|
fa2f1fe838 | ||
|
|
7e76b2da44 | ||
|
|
eba82a9b2f | ||
|
|
d66587ada1 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hub-guide",
|
"name": "code-guide",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "Comprehensive programming best-practice guide for all situations — monorepo, standalone, all languages and frameworks",
|
"description": "Comprehensive programming best-practice guide for all situations — monorepo, standalone, all languages and frameworks",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Asep Haryana Saputra",
|
"name": "Asep Haryana Saputra",
|
||||||
|
|||||||
@@ -1,39 +1,31 @@
|
|||||||
# hub-guide — Comprehensive Programming Best-Practice Plugin
|
# code-guide — Comprehensive Programming Best-Practice Plugin
|
||||||
|
|
||||||
A Claude Code plugin serving as a complete engineering guide for **all programming situations** — monorepo, standalone, any language, any framework.
|
A Claude Code plugin serving as a complete engineering guide for **all programming situations** — monorepo, standalone, any language, any framework.
|
||||||
|
|
||||||
|
All best-practice guides are **injected directly into the system prompt** at every session start via a `SessionStart` hook (same pattern as the `explanatory-output-style` plugin). No manual skill invocation needed — all guides are always active.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### 🧠 24 Best-Practice Skills
|
### 25 Best-Practice Guides (Always Active)
|
||||||
|
|
||||||
| Category | Skills |
|
| Category | Guides |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
| **Core Engineering** | clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance |
|
| **Core Engineering** | engineering-principles, clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance |
|
||||||
| **Languages** | typescript, python, rust, go |
|
| **Languages** | typescript, python, rust, go |
|
||||||
| **Frameworks** | react-frontend, elysiajs, hono-backend, drizzle-database, nextjs |
|
| **Frameworks** | react-frontend, elysiajs, hono-backend, drizzle-database, nextjs |
|
||||||
| **Infrastructure** | docker, ci-cd, monitoring |
|
| **Infrastructure** | docker, ci-cd, monitoring |
|
||||||
| **Monorepo** | monorepo (patterns + submodules + workspace tooling) |
|
| **Monorepo** | monorepo (patterns + submodules + workspace tooling) |
|
||||||
| **Hub-specific** | hub-guide (Asepharyana Hub monorepo infra & workflow) |
|
|
||||||
|
|
||||||
Skills activate automatically when Claude detects relevant context (language, framework, topic).
|
### SessionStart Hook (All Skills Injected)
|
||||||
|
|
||||||
### ⚡ Hooks for Auto-Detection
|
A SessionStart command hook reads **every** `skills/*/SKILL.md` file and injects all content into the conversation context at session start — wrapped in `<CODE_GUIDE_SKILLS>` tags (same pattern as `explanatory-output-style`'s `additionalContext` injection). All 25 guides are active from turn 1, no separate invocation needed.
|
||||||
|
|
||||||
| Hook | Trigger | What It Does |
|
|
||||||
|------|---------|--------------|
|
|
||||||
| **SessionStart** | Session begins | Detects project type from files (package.json, Cargo.toml, go.mod, etc.) and activates relevant skills |
|
|
||||||
| **PreToolUse** | Before Write/Edit | Detects file extension and injects language-specific best-practice rules |
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Quick Install
|
### Quick Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone
|
# From anywhere with access to the plugin directory
|
||||||
git clone https://github.com/asepharyana/asepharyana-hub-hub-guide.git
|
|
||||||
cd asepharyana-hub-hub-guide
|
|
||||||
|
|
||||||
# Install as one unit (recommended)
|
|
||||||
./install.sh
|
./install.sh
|
||||||
|
|
||||||
# Or symlink (edits in this repo are live)
|
# Or symlink (edits in this repo are live)
|
||||||
@@ -42,56 +34,35 @@ cd asepharyana-hub-hub-guide
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Skills are **auto-triggered** — Claude loads them when you mention relevant topics or work with matching file types.
|
All guides are **always present in context** — Claude automatically applies the relevant guidance based on the current task, file types, and project structure.
|
||||||
|
|
||||||
Example triggers:
|
Skills activate automatically when the task matches their domain:
|
||||||
- *"Refactor this function"* → `clean-code` activates
|
- *"Refactor this function"* → `clean-code` guides apply
|
||||||
- *"Write a test for this"* → `testing` activates
|
- *"Write a test for this"* → `testing` guides apply
|
||||||
- *"Design an API endpoint"* → `api-design` activates
|
- *"Design an API endpoint"* → `api-design` guides apply
|
||||||
- Working with `.ts` files → `typescript` activates
|
- Working with `.ts` files → `typescript` guides apply
|
||||||
- Project with `Cargo.toml` → `rust` activates (SessionStart hook)
|
- Project with `Cargo.toml` → `rust` guides apply
|
||||||
|
|
||||||
## Auto-Detection (Hooks)
|
|
||||||
|
|
||||||
When you start a session in a project, the SessionStart hook scans for:
|
|
||||||
|
|
||||||
| File | Skills Activated |
|
|
||||||
|------|-----------------|
|
|
||||||
| `tsconfig.json` | typescript, react-frontend, elysiajs, hono-backend, drizzle-database |
|
|
||||||
| `Cargo.toml` | rust |
|
|
||||||
| `go.mod` | go |
|
|
||||||
| `pyproject.toml` | python |
|
|
||||||
| `pnpm-workspace.yaml` | monorepo |
|
|
||||||
| `Dockerfile` | docker |
|
|
||||||
| `.github/workflows/` | ci-cd |
|
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
hub-guide/
|
code-guide/
|
||||||
├── .claude-plugin/
|
├── .claude-plugin/
|
||||||
│ └── plugin.json # Plugin manifest
|
│ └── plugin.json # Plugin manifest (no skills auto-discovery)
|
||||||
├── hooks/
|
├── hooks/
|
||||||
│ ├── hooks.json # Hook configuration
|
│ ├── hooks.json # SessionStart command hook config
|
||||||
│ └── scripts/
|
│ ├── run-hook.cmd # Cross-platform polyglot wrapper
|
||||||
│ ├── detect-project.sh # SessionStart auto-detection
|
│ └── session-start # Injects ALL skills/*/SKILL.md into context
|
||||||
│ └── detect-file-type.sh # PreToolWrite language detection
|
├── skills/ # Source files read by the SessionStart hook
|
||||||
├── skills/
|
│ ├── engineering-principles/
|
||||||
│ ├── clean-code/ # +23 more skill directories
|
│ ├── clean-code/
|
||||||
│ ├── ...
|
│ ├── ...
|
||||||
│ └── hub-guide/ # Existing hub monorepo guide
|
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## How It Works
|
||||||
|
|
||||||
Skills are in `skills/<name>/SKILL.md` format (modern Claude Code plugin convention). Each skill includes:
|
1. **SessionStart hook** runs `hooks/run-hook.cmd session-start`.
|
||||||
|
2. The hook script iterates over **all** `skills/*/SKILL.md` files.
|
||||||
- **Frontmatter** — `name` and `description` with specific trigger phrases
|
3. Each skill's content is combined and injected as `additionalContext` wrapped in `<CODE_GUIDE_SKILLS>` tags — same `hookSpecificOutput.additionalContext` pattern as `explanatory-output-style`.
|
||||||
- **Lean body** — key rules, examples, and anti-patterns (~1500-2000 words)
|
4. All 25 guides are **always present** in the system prompt — no separate skill invocation needed.
|
||||||
|
|
||||||
Hooks use bash scripts. Edits to hooks/scripts/ take effect immediately when installed as a symlink.
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
Based on patterns from [kana-best-practice-engineering](https://github.com/asepharyana/kana-best-practice-engineering).
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"matcher": "startup|clear|compact",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start",
|
||||||
|
"async": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
: << 'CMDBLOCK'
|
||||||
|
@echo off
|
||||||
|
REM Cross-platform polyglot wrapper for hook scripts.
|
||||||
|
REM On Windows: cmd.exe runs the batch portion, which finds and calls bash.
|
||||||
|
REM On Unix: the shell interprets this as a script (: is a no-op in bash).
|
||||||
|
REM
|
||||||
|
REM Usage: run-hook.cmd <script-name> [args...]
|
||||||
|
|
||||||
|
if "%~1"=="" (
|
||||||
|
echo run-hook.cmd: missing script name >&2
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "HOOK_DIR=%~dp0"
|
||||||
|
|
||||||
|
REM Try Git for Windows bash in standard locations
|
||||||
|
if exist "C:\Program Files\Git\bin\bash.exe" (
|
||||||
|
"C:\Program Files\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
|
)
|
||||||
|
if exist "C:\Program Files (x86)\Git\bin\bash.exe" (
|
||||||
|
"C:\Program Files (x86)\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Try bash on PATH
|
||||||
|
where bash >nul 2>nul
|
||||||
|
if %ERRORLEVEL% equ 0 (
|
||||||
|
bash "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
|
)
|
||||||
|
|
||||||
|
REM No bash found — exit silently
|
||||||
|
exit /b 0
|
||||||
|
CMDBLOCK
|
||||||
|
|
||||||
|
# Unix: run the named script directly
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
SCRIPT_NAME="$1"
|
||||||
|
shift
|
||||||
|
exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SessionStart hook for code-guide plugin.
|
||||||
|
# Reads ALL skills/*/SKILL.md files and injects them as additionalContext
|
||||||
|
# so every best-practice guide is always active from turn 1.
|
||||||
|
#
|
||||||
|
# Pattern: identical to explanatory-output-style's session-start hook
|
||||||
|
# which injects educational-output instructions into context.
|
||||||
|
# Instead of one static message, we dynamically iterate over all skills.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
# Build combined content from all SKILL.md files.
|
||||||
|
combined=""
|
||||||
|
while IFS= read -r -d '' skill_file; do
|
||||||
|
skill_name=$(basename "$(dirname "$skill_file")")
|
||||||
|
content=$(cat "$skill_file")
|
||||||
|
# Append skill with header separator — uses $'...' for real newlines
|
||||||
|
combined="${combined}"$'\n\n\n'"===== code-guide:${skill_name} ====="$'\n\n'"${content}"
|
||||||
|
done < <(find "${PLUGIN_ROOT}/skills" -name 'SKILL.md' -print0 | sort -z)
|
||||||
|
|
||||||
|
intro=$'You have the code-guide plugin loaded with all best-practice guides directly injected.\nThe following skills are ALWAYS active in context — no manual invocation needed.\nWhen the current task matches a skill\'s domain, apply its guidance automatically.\n'
|
||||||
|
|
||||||
|
full_context="<CODE_GUIDE_SKILLS>
|
||||||
|
${intro}
|
||||||
|
${combined}
|
||||||
|
</CODE_GUIDE_SKILLS>"
|
||||||
|
|
||||||
|
# JSON-escape the full context: \ → \\, " → \", newline → \n, etc.
|
||||||
|
escape_for_json() {
|
||||||
|
local s="$1"
|
||||||
|
s="${s//\\/\\\\}"
|
||||||
|
s="${s//\"/\\\"}"
|
||||||
|
s="${s//$'\n'/\\n}"
|
||||||
|
s="${s//$'\r'/\\r}"
|
||||||
|
s="${s//$'\t'/\\t}"
|
||||||
|
printf '%s' "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
escaped=$(escape_for_json "$full_context")
|
||||||
|
|
||||||
|
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
||||||
|
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$escaped"
|
||||||
|
else
|
||||||
|
printf '{"additionalContext":"%s"}\n' "$escaped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
+21
-68
@@ -1,82 +1,35 @@
|
|||||||
# install.ps1 — hub-guide plugin installer for Claude Code (Windows)
|
# install.ps1 — code-guide plugin installer for Claude Code (Windows)
|
||||||
|
#
|
||||||
|
# SessionStart hook injects ALL skills/*/SKILL.md into context at session start.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# .\install.ps1 # Install to %USERPROFILE%\.claude\plugins\
|
# .\install.ps1 # Install to %USERPROFILE%\.claude\skills\
|
||||||
# .\install.ps1 -Project # Install to .claude\plugins\ (project-scoped)
|
# .\install.ps1 -Link # Symlink (PowerShell Admin/Dev mode)
|
||||||
# .\install.ps1 -Link # Symlink (PowerShell Admin/Dev mode)
|
|
||||||
# .\install.ps1 -NoHooks # Skills only
|
|
||||||
# .\install.ps1 clean-code testing # Install specific skills
|
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[switch]$Project,
|
[switch]$Link
|
||||||
[switch]$Link,
|
|
||||||
[switch]$NoHooks,
|
|
||||||
[Parameter(Position=0, ValueFromRemainingArguments=$true)]
|
|
||||||
[string[]]$SkillFilter
|
|
||||||
)
|
)
|
||||||
|
|
||||||
$PluginName = "hub-guide"
|
$PluginName = "code-guide"
|
||||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$TargetDir = Join-Path $env:USERPROFILE ".claude\skills"
|
||||||
# Determine target
|
|
||||||
if ($Project) {
|
|
||||||
$TargetDir = Join-Path (Get-Location) ".claude\plugins"
|
|
||||||
} else {
|
|
||||||
$TargetDir = Join-Path $env:USERPROFILE ".claude\plugins"
|
|
||||||
}
|
|
||||||
|
|
||||||
$PluginDir = Join-Path $TargetDir $PluginName
|
$PluginDir = Join-Path $TargetDir $PluginName
|
||||||
|
|
||||||
Write-Host "📐 hub-guide installer" -ForegroundColor Cyan
|
Write-Host "code-guide installer" -ForegroundColor Cyan
|
||||||
|
|
||||||
# Ensure target exists
|
New-Item -ItemType Directory -Force -Path $TargetDir | Out-Null
|
||||||
New-Item -ItemType Directory -Force -Path $PluginDir | Out-Null
|
|
||||||
|
|
||||||
# Install manifest
|
|
||||||
Copy-Item -Path (Join-Path $ScriptDir ".claude-plugin") -Destination $PluginDir -Recurse -Force
|
|
||||||
|
|
||||||
# Install hooks
|
|
||||||
if (-not $NoHooks) {
|
|
||||||
Write-Host " hooks/ → $PluginDir\hooks\"
|
|
||||||
if ($Link) {
|
|
||||||
New-Item -ItemType SymbolicLink -Path "$PluginDir\hooks" -Target (Join-Path $ScriptDir "hooks") -Force | Out-Null
|
|
||||||
} else {
|
|
||||||
Copy-Item -Path (Join-Path $ScriptDir "hooks") -Destination $PluginDir -Recurse -Force
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install skills
|
|
||||||
if ($SkillFilter.Count -gt 0) {
|
|
||||||
$SkillDir = Join-Path $PluginDir "skills"
|
|
||||||
New-Item -ItemType Directory -Force -Path $SkillDir | Out-Null
|
|
||||||
foreach ($skill in $SkillFilter) {
|
|
||||||
$skillName = Split-Path $skill -Leaf
|
|
||||||
$src = Join-Path $ScriptDir "skills" $skillName
|
|
||||||
if (Test-Path $src) {
|
|
||||||
Write-Host " skills/$skillName/ → $SkillDir"
|
|
||||||
if ($Link) {
|
|
||||||
New-Item -ItemType SymbolicLink -Path (Join-Path $SkillDir $skillName) -Target $src -Force | Out-Null
|
|
||||||
} else {
|
|
||||||
Copy-Item -Path $src -Destination $SkillDir -Recurse -Force
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host " ⚠️ Skill '$skillName' not found at $src" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host " skills/ (all) → $PluginDir\skills\"
|
|
||||||
if ($Link) {
|
|
||||||
New-Item -ItemType SymbolicLink -Path (Join-Path $PluginDir "skills") -Target (Join-Path $ScriptDir "skills") -Force | Out-Null
|
|
||||||
} else {
|
|
||||||
Copy-Item -Path (Join-Path $ScriptDir "skills") -Destination $PluginDir -Recurse -Force
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "✅ hub-guide installed to $PluginDir" -ForegroundColor Green
|
|
||||||
if ($Link) {
|
if ($Link) {
|
||||||
Write-Host " (symlink — edits in this repo are live)"
|
Write-Host " symlink mode"
|
||||||
|
Remove-Item -Path $PluginDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
New-Item -ItemType SymbolicLink -Path $PluginDir -Target $ScriptDir -Force | Out-Null
|
||||||
|
Write-Host " (symlink — edits in this repo are live)"
|
||||||
|
} else {
|
||||||
|
Write-Host " copy mode"
|
||||||
|
Remove-Item -Path $PluginDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Copy-Item -Path $ScriptDir -Destination $PluginDir -Recurse -Force
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host " Restart Claude Code or run /reload to activate."
|
Write-Host "Done. Restart Claude Code or run /reload."
|
||||||
Write-Host " Skills auto-trigger when you work — no commands needed."
|
Write-Host "SessionStart hook injects all 25 best-practice guides into context."
|
||||||
|
|||||||
+8
-8
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install.sh — hub-guide installer for Claude Code
|
# install.sh — code-guide installer for Claude Code
|
||||||
# Installs the entire hub-guide directory as one unit into ~/.claude/skills/.
|
# Installs the entire code-guide directory as one unit into ~/.claude/skills/.
|
||||||
# Skills auto-discover, hooks auto-load from hooks/hooks.json.
|
# SessionStart hook injects ALL skills/*/SKILL.md into context at session start.
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./install.sh # Copy hub-guide to ~/.claude/skills/
|
# ./install.sh # Copy code-guide to ~/.claude/skills/
|
||||||
# ./install.sh --link # Symlink (edits live)
|
# ./install.sh --link # Symlink (edits live)
|
||||||
# Requires: Claude Code
|
# Requires: Claude Code
|
||||||
|
|
||||||
@@ -20,12 +20,12 @@ for arg in "$@"; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "hub-guide installer"
|
echo "code-guide installer"
|
||||||
echo " target: ${SKILLS_DIR}/hub-guide/"
|
echo " target: ${SKILLS_DIR}/code-guide/"
|
||||||
echo " mode: $([ "$LINK_MODE" = true ] && echo 'symlink' || echo 'copy')"
|
echo " mode: $([ "$LINK_MODE" = true ] && echo 'symlink' || echo 'copy')"
|
||||||
|
|
||||||
mkdir -p "$SKILLS_DIR"
|
mkdir -p "$SKILLS_DIR"
|
||||||
DST="${SKILLS_DIR}/hub-guide"
|
DST="${SKILLS_DIR}/code-guide"
|
||||||
|
|
||||||
if [ "$LINK_MODE" = true ]; then
|
if [ "$LINK_MODE" = true ]; then
|
||||||
rm -rf "$DST"
|
rm -rf "$DST"
|
||||||
@@ -41,4 +41,4 @@ if [ "$LINK_MODE" = true ]; then
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo "Done. Restart Claude Code or run /reload."
|
echo "Done. Restart Claude Code or run /reload."
|
||||||
echo "Skills auto-trigger. Hooks auto-load from hooks/hooks.json."
|
echo "SessionStart hook injects all 25 best-practice guides into context."
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# setup-hooks.sh — Configure hub-guide hooks in Claude Code
|
|
||||||
#
|
|
||||||
# Adds hub-guide hooks to ~/.claude/settings.local.json
|
|
||||||
# This allows Claude to auto-detect your project and suggest relevant skills.
|
|
||||||
#
|
|
||||||
# Usage: ./setup-hooks.sh
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
SETTINGS_FILE="${HOME}/.claude/settings.local.json"
|
|
||||||
|
|
||||||
echo "📐 hub-guide hooks setup"
|
|
||||||
|
|
||||||
python3 << PYEOF
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
settings_file = os.path.expanduser("~/.claude/settings.local.json")
|
|
||||||
|
|
||||||
# Read existing or create empty
|
|
||||||
if os.path.exists(settings_file):
|
|
||||||
with open(settings_file) as f:
|
|
||||||
cfg = json.load(f)
|
|
||||||
else:
|
|
||||||
cfg = {}
|
|
||||||
|
|
||||||
# Build hooks config
|
|
||||||
cfg["hooks"] = {
|
|
||||||
"SessionStart": [{
|
|
||||||
"hooks": [{
|
|
||||||
"type": "command",
|
|
||||||
"command": f"bash \"{script_dir}/hooks/scripts/detect-project.sh\"",
|
|
||||||
"timeout": 10
|
|
||||||
}]
|
|
||||||
}],
|
|
||||||
"PreToolUse": [{
|
|
||||||
"matcher": "Write|Edit",
|
|
||||||
"hooks": [{
|
|
||||||
"type": "command",
|
|
||||||
"command": f"bash \"{script_dir}/hooks/scripts/detect-file-type.sh\" \"\$TOOL_INPUT\"",
|
|
||||||
"timeout": 10
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ensure parent dir exists
|
|
||||||
os.makedirs(os.path.dirname(settings_file), exist_ok=True)
|
|
||||||
|
|
||||||
with open(settings_file, 'w') as f:
|
|
||||||
json.dump(cfg, f, indent=2)
|
|
||||||
|
|
||||||
print(f"✅ Hooks configured in {settings_file}")
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " Restart Claude Code or run /reload to activate hooks."
|
|
||||||
@@ -55,16 +55,16 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
service: [scraper, hub]
|
service: [frontend, api]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- run: |
|
||||||
docker build \
|
docker build \
|
||||||
-f infra/docker/${{ matrix.service }}.Dockerfile \
|
-f infra/docker/${{ matrix.service }}.Dockerfile \
|
||||||
-t ghcr.io/.../${{ matrix.service }}:sha-${{ github.sha }} \
|
-t ghcr.io/myorg/myproject/${{ matrix.service }}:sha-${{ github.sha }} \
|
||||||
-t ghcr.io/.../${{ matrix.service }}:latest \
|
-t ghcr.io/myorg/myproject/${{ matrix.service }}:latest \
|
||||||
.
|
.
|
||||||
- run: docker push --all-tags ghcr.io/.../${{ matrix.service }}
|
- run: docker push --all-tags ghcr.io/myorg/myproject/${{ matrix.service }}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Deploy (after build)**
|
**Deploy (after build)**
|
||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
service: [scraper, hub, api]
|
service: [frontend, api, worker]
|
||||||
fail-fast: false # Let others complete even if one fails
|
fail-fast: false # Let others complete even if one fails
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -149,10 +149,10 @@ secrets:
|
|||||||
6. **Security scan** (<5 min) — CodeQL, dependency audit, secret scan
|
6. **Security scan** (<5 min) — CodeQL, dependency audit, secret scan
|
||||||
7. **Deploy** (<2 min) — SSH, pull, restart
|
7. **Deploy** (<2 min) — SSH, pull, restart
|
||||||
|
|
||||||
## Deployment (this repo's pattern)
|
## Deployment Pattern
|
||||||
|
|
||||||
1. SSH to VPS (`orangevps`)
|
1. SSH to deployment target
|
||||||
2. Pull latest images from GHCR
|
2. Pull latest images from container registry
|
||||||
3. Restart specific container (not all)
|
3. Restart specific container (not all)
|
||||||
4. Health check after restart
|
4. Health check after restart
|
||||||
5. Rollback if health check fails
|
5. Rollback if health check fails
|
||||||
|
|||||||
+10
-12
@@ -76,12 +76,11 @@ networks:
|
|||||||
external: true
|
external: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Typical Services Layout (this repo)
|
### Typical Docker Compose Order
|
||||||
- **Redis:** `shared.yml` — always first
|
- **Data layer:** `db.yml`, `redis.yml` — stateful services first
|
||||||
- **NATS:** `nats.yml` — JetStream-enabled
|
- **Messaging:** `nats.yml`, `rabbitmq.yml` — message brokers
|
||||||
- **Dapr:** `dapr.yml` — placement service
|
- **Infrastructure:** `traefik.yml`, `nginx.yml` — reverse proxy
|
||||||
- **Traefik:** `traefik.yml` — reverse proxy
|
- **Application:** `app.yml` — service containers
|
||||||
- **App + Sidecar:** `app.yml` — service + daprd sidecar
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
@@ -99,17 +98,16 @@ networks:
|
|||||||
sha-<short-sha> # Immutable — for deterministic rollbacks
|
sha-<short-sha> # Immutable — for deterministic rollbacks
|
||||||
latest # Mutable — convenience
|
latest # Mutable — convenience
|
||||||
|
|
||||||
# Example (from this repo's CI)
|
# Example
|
||||||
ghcr.io/asepharyana/asepharyana-hub/<service>:sha-a1b2c3d
|
ghcr.io/myorg/myproject/<service>:sha-a1b2c3d
|
||||||
ghcr.io/asepharyana/asepharyana-hub/<service>:latest
|
ghcr.io/myorg/myproject/<service>:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## Networking
|
## Networking
|
||||||
|
|
||||||
- **All containers** join `app-shared-net` (external Docker bridge).
|
- **All containers** join the same Docker network (external bridge).
|
||||||
- **DNS resolution** via Docker DNS (container name = hostname).
|
- **DNS resolution** via Docker DNS (container name = hostname).
|
||||||
- **Cross-VPS** via Tailscale (`100.64.0.0/10`).
|
- **Expose only needed ports** — reverse proxy handles external traffic on port 443.
|
||||||
- **Expose only needed ports** — Traefik handles external traffic on port 443.
|
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
|
|||||||
@@ -312,4 +312,4 @@ Never guess, speculate, or assume. **Every claim, suggestion, or piece of code y
|
|||||||
✅ "I searched for .env and didn't find one. There's a .env.example — maybe that's the template. Could you check?"
|
✅ "I searched for .env and didn't find one. There's a .env.example — maybe that's the template. Could you check?"
|
||||||
|
|
||||||
❌ "Dockerfiles are usually in the root"
|
❌ "Dockerfiles are usually in the root"
|
||||||
✅ "I found the Dockerfile at: infra/docker/hub.Dockerfile"
|
✅ "I found the Dockerfile at: infra/docker/app.Dockerfile"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: hono-backend
|
name: hono-backend
|
||||||
description: Hono best practices — middleware, RPC, validation (Zod), Drizzle integration, and project organization. Use when building Hono backend APIs. Triggers when working with this framework's files and patterns, not just explicit mentions.js," "hono RPC," "hono middleware," "hono Zod OpenAPI," "hono validation," or "hono Drizzle."
|
description: Use when building Hono backend APIs — middleware, RPC, Zod validation, Drizzle integration, and project organization. Triggers from Hono file patterns and project config.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Hono Backend Best Practices
|
# Hono Backend Best Practices
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
---
|
|
||||||
name: hub-guide
|
|
||||||
description: Guide for the Asepharyana Hub monorepo — submodule workflow, infrastructure stack (Traefik, Dapr, NATS, Redis), CI/CD pipelines, adding new services, and debugging tips. Use when working in the asepharyana-hub monorepo, managing submodules, dealing with Docker/infra setup. Detects from code context and project files — not dependent on specific language keywords."
|
|
||||||
---
|
|
||||||
|
|
||||||
# Hub Guide — Asepharyana Hub Monorepo
|
|
||||||
|
|
||||||
## Submodule Workflow
|
|
||||||
|
|
||||||
- Code changes go in the submodule repo, not here. The hub monorepo only tracks submodule pointers.
|
|
||||||
- After pushing changes to a submodule repo, update the pointer here:
|
|
||||||
```bash
|
|
||||||
cd apps/<name> && git checkout main && git pull
|
|
||||||
cd ../.. && git add apps/<name> && git commit -m "chore(deps): update <name> submodule"
|
|
||||||
```
|
|
||||||
- CI/CD auto-updates submodule pointers via `repository_dispatch`. Manual updates are fine for dev.
|
|
||||||
|
|
||||||
### Typical Submodule State
|
|
||||||
|
|
||||||
| State | Meaning |
|
|
||||||
|-------|---------|
|
|
||||||
| `(HEAD)` | Detached HEAD — submodule is at the committed pointer |
|
|
||||||
| `(main)` | On the default branch — you've done `cd apps/name && git checkout main` |
|
|
||||||
| Dirty | Uncommitted changes inside submodule |
|
|
||||||
|
|
||||||
To reset a submodule to its committed pointer:
|
|
||||||
```bash
|
|
||||||
git submodule update --init --recursive apps/<name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Quickstart
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make init-submodules # After fresh clone — fetches all submodules
|
|
||||||
make dev # Start Redis for local dev
|
|
||||||
docker compose -f infra/compose/shared.yml up -d # Full infra stack
|
|
||||||
```
|
|
||||||
|
|
||||||
## Local vs Production
|
|
||||||
|
|
||||||
| Aspect | Local | Production (VPS) |
|
|
||||||
|--------|-------|------------------|
|
|
||||||
| DB | None (or local) | PostgreSQL on `imrnes` via Tailscale |
|
|
||||||
| Redis | `make dev` | Container on `orangevps` |
|
|
||||||
| Traefik | Not running | TLS-terminated on `orangevps` |
|
|
||||||
| DNS | `localhost` | `*.asepharyana.my.id`, `*.asepharya.web.id` |
|
|
||||||
|
|
||||||
## Debugging Tips
|
|
||||||
|
|
||||||
### Docker compose validation
|
|
||||||
```bash
|
|
||||||
for f in infra/compose/*.yml; do docker compose -f "$f" config >/dev/null && echo "OK $f"; done
|
|
||||||
```
|
|
||||||
|
|
||||||
### YAML syntax check
|
|
||||||
```bash
|
|
||||||
python -c "import pathlib, yaml; [yaml.safe_load(open(p)) for p in pathlib.Path('infra').rglob('*.yml')]"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check submodule pointers
|
|
||||||
```bash
|
|
||||||
git submodule status
|
|
||||||
# Leading `-` = not initialized, `+` = different from committed hash, ` ` = matches
|
|
||||||
```
|
|
||||||
|
|
||||||
### Traefik route not working?
|
|
||||||
1. Check `infra/traefik/dynamic/apps.yaml` — router rule + service definition present?
|
|
||||||
2. Container labels in compose file include Traefik config?
|
|
||||||
3. Container on `app-shared-net`?
|
|
||||||
|
|
||||||
## Adding a New Service — Checklist
|
|
||||||
|
|
||||||
1. [ ] Create separate repo for app code
|
|
||||||
2. [ ] `git submodule add <url> apps/<name>`
|
|
||||||
3. [ ] Create Dockerfile in `infra/docker/`
|
|
||||||
4. [ ] Create compose file in `infra/compose/` (app + Dapr sidecar)
|
|
||||||
5. [ ] Add Traefik router in `infra/traefik/dynamic/apps.yaml`
|
|
||||||
6. [ ] Add build job in `.github/workflows/docker-build-push.yml`
|
|
||||||
7. [ ] Verify: `docker compose -f infra/compose/<name>.yml config`
|
|
||||||
|
|
||||||
See `docs/add-new-app.md` for full guide.
|
|
||||||
|
|
||||||
## Monitoring
|
|
||||||
|
|
||||||
- **Dashboard**: `/dashboard` on the hub site (auto-refresh 15s)
|
|
||||||
- **Dashboard API**: `/api/dashboard` — JSON with containers, traces, metrics
|
|
||||||
- **Prometheus**: Auto-discovers containers with `prometheus.io/scrape=true` label via Docker SD
|
|
||||||
- **Jaeger**: Traces via OTLP — check for cross-service latency
|
|
||||||
|
|
||||||
## Infrastructure Files Map
|
|
||||||
|
|
||||||
| Path | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `infra/compose/*.yml` | One Docker Compose file per service |
|
|
||||||
| `infra/dapr/components/` | Dapr pub/sub, state store component configs |
|
|
||||||
| `infra/docker/*.Dockerfile` | Build files per service |
|
|
||||||
| `infra/traefik/dynamic/apps.yaml` | Traefik route definitions |
|
|
||||||
| `infra/traefik/traefik.yml` | Traefik static config (entrypoints, providers) |
|
|
||||||
| `.github/workflows/` | CI/CD pipelines |
|
|
||||||
| `docs/` | ADRs, deployment guide, new-app guide |
|
|
||||||
|
|
||||||
## Git Hook Scripts
|
|
||||||
|
|
||||||
Located in `scripts/`:
|
|
||||||
- `scripts/cleanup.sh` — prune old Docker images, clean temp files
|
|
||||||
- `scripts/update-deps.sh` — bump dependencies across submodules
|
|
||||||
- `scripts/setup-hooks.sh` — install local git hooks
|
|
||||||
+10
-10
@@ -19,8 +19,8 @@ description: Monorepo best practices — tooling, workspace configuration, share
|
|||||||
|
|
||||||
```
|
```
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── hub/ # Next.js app (submodule)
|
│ ├── app1/ # Application (submodule)
|
||||||
│ └── scraper/ # Rust API (submodule)
|
│ └── app2/ # Another application (submodule)
|
||||||
├── packages/ # Shared libraries (when not submodules)
|
├── packages/ # Shared libraries (when not submodules)
|
||||||
├── infra/ # Shared infra config
|
├── infra/ # Shared infra config
|
||||||
├── pnpm-workspace.yaml
|
├── pnpm-workspace.yaml
|
||||||
@@ -61,13 +61,13 @@ pnpm -r run build
|
|||||||
- **Explicit `dependencies`** — never rely on hoisting.
|
- **Explicit `dependencies`** — never rely on hoisting.
|
||||||
- **Lock file** (`pnpm-lock.yaml`) committed — immutable installs.
|
- **Lock file** (`pnpm-lock.yaml`) committed — immutable installs.
|
||||||
|
|
||||||
## Git Submodules (this repo's pattern)
|
## Git Submodules
|
||||||
|
|
||||||
```
|
```
|
||||||
asepharyana-hub/
|
my-monorepo/
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── hub/ → asepharyana/asepharyana-hub-hub
|
│ ├── app1/ → org/app1-repo
|
||||||
│ └── scraper/ → asepharyana/asepharyana-hub-scraper
|
│ └── app2/ → org/app2-repo
|
||||||
```
|
```
|
||||||
|
|
||||||
### Submodule Workflow
|
### Submodule Workflow
|
||||||
@@ -79,8 +79,8 @@ git submodule update --init --recursive
|
|||||||
git submodule foreach git pull origin main
|
git submodule foreach git pull origin main
|
||||||
|
|
||||||
# Update one submodule
|
# Update one submodule
|
||||||
cd apps/hub && git checkout main && git pull
|
cd apps/app1 && git checkout main && git pull
|
||||||
cd ../.. && git add apps/hub && git commit -m "chore(deps): update hub submodule"
|
cd ../.. && git add apps/app1 && git commit -m "chore(deps): update app1 submodule"
|
||||||
git push
|
git push
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -107,8 +107,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- 'apps/hub/**'
|
- 'apps/app1/**'
|
||||||
- 'infra/docker/hub.Dockerfile'
|
- 'infra/docker/app1.Dockerfile'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Affected Commands (Nx/Turborepo/Moon)
|
### Affected Commands (Nx/Turborepo/Moon)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: nextjs
|
name: nextjs
|
||||||
description: Next.js App Router best practices — server components, client components, data fetching, routing, middleware, and deployment. Use when building Next.js applications. Triggers when working with this framework's files and patterns, not just explicit mentions.js," "App Router," "server component," "client component," "SSR," "SSG," "ISR," "Middleware," "layout," "page," "route handler," "next/navigation," or "server actions."
|
description: Use when building Next.js App Router applications — server components, client components, data fetching, routing, middleware, and deployment. Triggers from next.config, layout.tsx, page.tsx, and framework file patterns.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Next.js Best Practices
|
# Next.js Best Practices
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: react-frontend
|
name: react-frontend
|
||||||
description: React and frontend best practices — component patterns, hooks, state management, TanStack Query, React Router, performance, and testing. Use when building React components, designing state management. Triggers when working with this framework's files and patterns, not just explicit mentions.js," or "Frontend."
|
description: Use when building React components — hooks, state management, TanStack Query, React Router, performance patterns, and testing. Triggers from .tsx/.jsx files and framework file patterns.
|
||||||
---
|
---
|
||||||
|
|
||||||
# React Frontend Best Practices
|
# React Frontend Best Practices
|
||||||
|
|||||||
Reference in New Issue
Block a user