Merge remote-tracking branch 'origin/dev' into claude/skills-plugins-audit-vrttx1

# Conflicts:
#	.claude-plugin/marketplace.json
#	.codex/skills-index.json
This commit is contained in:
Claude 2026-06-11 03:59:40 +00:00
commit 2e600b6454
No known key found for this signature in database
23 changed files with 1870 additions and 358 deletions

View file

@ -1651,6 +1651,24 @@
"compliance"
],
"category": "compliance"
},
{
"name": "collab-proof",
"source": "./engineering/collab-proof",
"description": "Assisted retrospective: after a session, calibrates what Claude contributed vs what the developer drove. LLM-assessed 4-frame analysis with explicit rubric, zero dependencies.",
"version": "1.0.0",
"author": {
"name": "dong7812",
"url": "https://github.com/dong7812"
},
"keywords": [
"ai-collaboration",
"session-retrospective",
"git-analysis",
"decision-logging",
"collab-proof"
],
"category": "engineering"
}
]
}

1
.codex/skills/collab-proof Symbolic link
View file

@ -0,0 +1 @@
../../engineering/collab-proof/skills/collab-proof

View file

@ -36,10 +36,10 @@ npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor --agent
```bash
# CEO Advisor
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/ceo-advisor
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/skills/ceo-advisor
# CTO Advisor
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/cto-advisor
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/skills/cto-advisor
```
**Supported Agents:** Claude Code, Cursor, VS Code, Copilot, Goose, Amp, Codex
@ -148,7 +148,7 @@ This C-Level advisory skills collection provides executive leadership guidance f
1. **Install CEO Advisor:**
```bash
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/ceo-advisor
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/skills/ceo-advisor
```
2. **Evaluate Strategic Initiative:**
@ -170,7 +170,7 @@ This C-Level advisory skills collection provides executive leadership guidance f
1. **Install CTO Advisor:**
```bash
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/cto-advisor
npx ai-agent-skills install alirezarezvani/claude-skills/c-level-advisor/skills/cto-advisor
```
2. **Analyze Technical Debt:**

View file

@ -23,30 +23,30 @@ npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team --agen
```bash
# Core Engineering
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-architect
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-frontend
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-backend
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-fullstack
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-qa
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-devops
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-secops
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/code-reviewer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-security
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-architect
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-frontend
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-backend
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-fullstack
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-qa
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-devops
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-secops
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/code-reviewer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-security
# Cloud & Enterprise
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/aws-solution-architect
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/ms365-tenant-manager
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/aws-solution-architect
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/ms365-tenant-manager
# Development Tools
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/tdd-guide
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/tech-stack-evaluator
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/tdd-guide
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/tech-stack-evaluator
# AI/ML/Data
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-data-scientist
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-data-engineer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-ml-engineer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-prompt-engineer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/senior-computer-vision
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-data-scientist
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-data-engineer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-ml-engineer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-prompt-engineer
npx ai-agent-skills install alirezarezvani/claude-skills/engineering-team/skills/senior-computer-vision
```
**Supported Agents:** Claude Code, Cursor, VS Code, Copilot, Goose, Amp, Codex

View file

@ -20,8 +20,8 @@ python scripts/pipeline_generator.py ./app --platform=github --stages=build,test
# Script 2: Terraform Scaffolder — generates and validates IaC modules for AWS/GCP/Azure
python scripts/terraform_scaffolder.py ./infra --provider=aws --module=ecs-service --verbose
# Script 3: Deployment Manager — orchestrates container deployments with rollback support
python3 scripts/deployment_manager.py ./deploy --verbose --json
# Script 3: Deployment Manager — generates deployment manifests + runbooks with rollback support
python3 scripts/deployment_manager.py deploy --env=staging --image=app:1.2.3 --strategy=blue-green --verbose --json
```
## Core Capabilities
@ -147,7 +147,7 @@ python scripts/terraform_scaffolder.py <target-path> --provider=aws|gcp|azure --
### 3. Deployment Manager
Orchestrates deployments with blue/green or rolling strategies, health-check gates, and automatic rollback on failure.
Generates Kubernetes deployment manifests and ordered kubectl runbooks for blue/green or rolling strategies, with health-check gates before traffic switches and rollback runbooks. The tool writes manifests and prints the commands — it never applies them to a cluster itself, so every change gets a human review.
**Example — Kubernetes blue/green deployment (blue-slot specific elements):**
```yaml

View file

@ -1,114 +1,273 @@
#!/usr/bin/env python3
"""
Deployment Manager
Automated tool for senior devops tasks
Generates blue/green or rolling Kubernetes deployment manifests plus an ordered
runbook of kubectl commands, and audits existing manifests. It never talks to a
cluster itself review the manifests and run the printed commands yourself.
Subcommands:
deploy --env --image [--strategy] [--health-check-url] write manifests + runbook
rollback --env --to-version write a rollback runbook
analyze --env audit manifests on disk
"""
import os
import sys
import json
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, List
DEPLOYMENT_TEMPLATE = """apiVersion: apps/v1
kind: Deployment
metadata:
name: {name}
namespace: {env}
labels:
app: {app}{slot_label}
spec:
replicas: {replicas}
selector:
matchLabels:
app: {app}{slot_label_indented}
template:
metadata:
labels:
app: {app}{slot_label_indented2}
spec:
containers:
- name: app
image: {image}
readinessProbe:
httpGet:
path: {health_path}
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
"""
SERVICE_TEMPLATE = """apiVersion: v1
kind: Service
metadata:
name: {app}-svc
namespace: {env}
spec:
selector:
app: {app}{slot_selector}
ports:
- port: 80
targetPort: 8080
"""
def app_name_from_image(image: str) -> str:
"""ghcr.io/org/my-app:1.2.3 -> my-app"""
repo = image.rsplit(":", 1)[0]
return repo.rsplit("/", 1)[-1] or "app"
def render_deployment(app: str, env: str, image: str, replicas: int,
health_path: str, slot: str = "") -> str:
return DEPLOYMENT_TEMPLATE.format(
name=f"{app}-{slot}" if slot else app,
env=env,
app=app,
image=image,
replicas=replicas,
health_path=health_path,
slot_label=f"\n slot: {slot}" if slot else "",
slot_label_indented=f"\n slot: {slot}" if slot else "",
slot_label_indented2=f"\n slot: {slot}" if slot else "",
)
def cmd_deploy(args) -> Dict:
app = args.app or app_name_from_image(args.image)
health_path = "/healthz"
if args.health_check_url:
match = re.search(r"https?://[^/]+(/.*)", args.health_check_url)
if match:
health_path = match.group(1)
out_dir = Path(args.output_dir) / args.env
out_dir.mkdir(parents=True, exist_ok=True)
written: List[str] = []
runbook: List[str] = []
if args.strategy == "blue-green":
slot = args.slot
manifest = out_dir / f"deployment-{slot}.yaml"
manifest.write_text(
render_deployment(app, args.env, args.image, args.replicas, health_path, slot),
encoding="utf-8",
)
written.append(str(manifest))
service = out_dir / "service.yaml"
if not service.exists():
# service starts pointing at the OTHER slot; traffic switches in the runbook
other = "green" if slot == "blue" else "blue"
service.write_text(SERVICE_TEMPLATE.format(
app=app, env=args.env, slot_selector=f"\n slot: {other}"), encoding="utf-8")
written.append(str(service))
runbook = [
f"kubectl apply -f {manifest}",
f"kubectl rollout status deployment/{app}-{slot} -n {args.env}",
]
if args.health_check_url:
runbook.append(f"curl -sf {args.health_check_url} || echo 'HEALTH CHECK FAILED — do not switch traffic'")
runbook += [
f"# switch traffic to the {slot} slot only after the checks above pass:",
f"kubectl patch service {app}-svc -n {args.env} "
f"-p '{{\"spec\":{{\"selector\":{{\"app\":\"{app}\",\"slot\":\"{slot}\"}}}}}}'",
]
else: # rolling
manifest = out_dir / "deployment.yaml"
manifest.write_text(
render_deployment(app, args.env, args.image, args.replicas, health_path),
encoding="utf-8",
)
written.append(str(manifest))
service = out_dir / "service.yaml"
if not service.exists():
service.write_text(SERVICE_TEMPLATE.format(
app=app, env=args.env, slot_selector=""), encoding="utf-8")
written.append(str(service))
runbook = [
f"kubectl apply -f {manifest}",
f"kubectl rollout status deployment/{app} -n {args.env}",
]
if args.health_check_url:
runbook.append(f"curl -sf {args.health_check_url} || kubectl rollout undo deployment/{app} -n {args.env}")
return {
"status": "success",
"action": "deploy",
"env": args.env,
"app": app,
"image": args.image,
"strategy": args.strategy,
"manifests_written": written,
"runbook": runbook,
}
def cmd_rollback(args) -> Dict:
app = args.app
runbook = [
f"# Option 1 — pin the previous image version explicitly:",
f"kubectl set image deployment/{app} app={app}:{args.to_version} -n {args.env}",
f"kubectl rollout status deployment/{app} -n {args.env}",
f"# Option 2 — revert to the previous ReplicaSet:",
f"kubectl rollout undo deployment/{app} -n {args.env}",
f"# Verify:",
f"kubectl get pods -n {args.env} -l app={app}",
]
return {
"status": "success",
"action": "rollback",
"env": args.env,
"app": app,
"to_version": args.to_version,
"runbook": runbook,
}
def cmd_analyze(args) -> Dict:
env_dir = Path(args.output_dir) / args.env
deployments = []
if env_dir.is_dir():
for manifest in sorted(env_dir.glob("deployment*.yaml")):
text = manifest.read_text(encoding="utf-8")
image = re.search(r"image:\s*(\S+)", text)
replicas = re.search(r"replicas:\s*(\d+)", text)
slot = re.search(r"slot:\s*(\S+)", text)
deployments.append({
"manifest": str(manifest),
"image": image.group(1) if image else "unknown",
"replicas": int(replicas.group(1)) if replicas else 0,
"slot": slot.group(1) if slot else None,
})
return {
"status": "success" if deployments else "empty",
"action": "analyze",
"env": args.env,
"manifest_dir": str(env_dir),
"deployments": deployments,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Generate deployment manifests and runbooks (blue/green or rolling)."
)
sub = parser.add_subparsers(dest="command", required=True)
deploy = sub.add_parser("deploy", help="Generate deployment manifests + runbook")
deploy.add_argument("--env", required=True, help="Target environment / namespace")
deploy.add_argument("--image", required=True, help="Container image (repo:tag)")
deploy.add_argument("--strategy", default="rolling", choices=["blue-green", "rolling"])
deploy.add_argument("--health-check-url", help="Health check URL gating traffic switch")
deploy.add_argument("--app", help="App name (default: derived from image)")
deploy.add_argument("--slot", default="blue", choices=["blue", "green"],
help="Slot to deploy into (blue-green only)")
deploy.add_argument("--replicas", type=int, default=3)
deploy.add_argument("--output-dir", default="./deploy", help="Manifest output directory")
rollback = sub.add_parser("rollback", help="Generate a rollback runbook")
rollback.add_argument("--env", required=True)
rollback.add_argument("--to-version", required=True, help="Version to roll back to")
rollback.add_argument("--app", default="app", help="App / deployment name")
analyze = sub.add_parser("analyze", help="Audit deployment manifests on disk")
analyze.add_argument("--env", required=True)
analyze.add_argument("--output-dir", default="./deploy", help="Manifest directory")
for p in (deploy, rollback, analyze):
p.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
p.add_argument("--json", action="store_true", help="Output results as JSON")
p.add_argument("--output", "-o", help="Write JSON results to this file")
return parser
class DeploymentManager:
"""Main class for deployment manager functionality"""
def __init__(self, target_path: str, verbose: bool = False):
self.target_path = Path(target_path)
self.verbose = verbose
self.results = {}
def run(self) -> Dict:
"""Execute the main functionality"""
print(f"🚀 Running {self.__class__.__name__}...")
print(f"📁 Target: {self.target_path}")
try:
self.validate_target()
self.analyze()
self.generate_report()
print("✅ Completed successfully!")
return self.results
except Exception as e:
print(f"❌ Error: {e}")
sys.exit(1)
def validate_target(self):
"""Validate the target path exists and is accessible"""
if not self.target_path.exists():
raise ValueError(f"Target path does not exist: {self.target_path}")
if self.verbose:
print(f"✓ Target validated: {self.target_path}")
def analyze(self):
"""Perform the main analysis or operation"""
if self.verbose:
print("📊 Analyzing...")
# Main logic here
self.results['status'] = 'success'
self.results['target'] = str(self.target_path)
self.results['findings'] = []
# Add analysis results
if self.verbose:
print(f"✓ Analysis complete: {len(self.results.get('findings', []))} findings")
def generate_report(self):
"""Generate and display the report"""
print("\n" + "="*50)
print("REPORT")
print("="*50)
print(f"Target: {self.results.get('target')}")
print(f"Status: {self.results.get('status')}")
print(f"Findings: {len(self.results.get('findings', []))}")
print("="*50 + "\n")
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(
description="Deployment Manager"
)
parser.add_argument(
'target',
help='Target path to analyze or process'
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='Enable verbose output'
)
parser.add_argument(
'--json',
action='store_true',
help='Output results as JSON'
)
parser.add_argument(
'--output', '-o',
help='Output file path'
)
args = parser.parse_args()
tool = DeploymentManager(
args.target,
verbose=args.verbose
)
results = tool.run()
if args.json:
# support the documented `--analyze --env=...` flag form as an alias
argv = ["analyze" if a == "--analyze" else a for a in sys.argv[1:]]
args = build_parser().parse_args(argv)
handlers = {"deploy": cmd_deploy, "rollback": cmd_rollback, "analyze": cmd_analyze}
results = handlers[args.command](args)
print(f"🚀 {results['action']} ({results['env']}) — status: {results['status']}")
for manifest in results.get("manifests_written", []):
print(f"✓ Wrote {manifest}")
for dep in results.get("deployments", []):
slot = f" slot={dep['slot']}" if dep["slot"] else ""
print(f" - {dep['manifest']}: image={dep['image']} replicas={dep['replicas']}{slot}")
if results.get("runbook"):
print("\nRunbook — review, then execute in order:")
for step in results["runbook"]:
print(f" {step}")
if args.json or args.output:
output = json.dumps(results, indent=2)
if args.output:
with open(args.output, 'w') as f:
f.write(output)
Path(args.output).write_text(output, encoding="utf-8")
print(f"Results written to {args.output}")
else:
print(output)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -1,114 +1,238 @@
#!/usr/bin/env python3
"""
Pipeline Generator
Automated tool for senior devops tasks
Scaffolds CI/CD pipeline configurations for GitHub Actions or CircleCI with
build, test, security, and deploy stages. Detects node/python/go projects to
pick sensible default commands.
"""
import os
import sys
import json
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, List
VALID_STAGES = ["build", "test", "security", "deploy"]
def detect_runtime(project: Path) -> str:
if (project / "package.json").exists():
return "node"
if (project / "pyproject.toml").exists() or (project / "requirements.txt").exists():
return "python"
if (project / "go.mod").exists():
return "go"
return "generic"
RUNTIME_COMMANDS: Dict[str, Dict[str, List[str]]] = {
"node": {
"setup": ["npm ci"],
"build": ["npm run build --if-present"],
"test": ["npm run lint --if-present", "npm test"],
},
"python": {
"setup": ["pip install -r requirements.txt"],
"build": ["python -m compileall ."],
"test": ["python -m ruff check .", "python -m pytest"],
},
"go": {
"setup": ["go mod download"],
"build": ["go build ./..."],
"test": ["go vet ./...", "go test ./..."],
},
"generic": {
"setup": ["echo 'add setup commands here'"],
"build": ["echo 'add build commands here'"],
"test": ["echo 'add test commands here'"],
},
}
GITHUB_SETUP_STEPS = {
"node": """ - uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'""",
"python": """ - uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'""",
"go": """ - uses: actions/setup-go@v5
with:
go-version: '1.22'""",
"generic": "",
}
CIRCLECI_IMAGES = {
"node": "cimg/node:20.11",
"python": "cimg/python:3.12",
"go": "cimg/go:1.22",
"generic": "cimg/base:current",
}
def github_job(name: str, runtime: str, commands: List[str], needs: List[str],
extra: str = "") -> str:
lines = [f" {name}:"]
if needs:
lines.append(f" needs: [{', '.join(needs)}]")
if name == "deploy":
lines.append(" if: github.ref == 'refs/heads/main'")
lines.append(" runs-on: ubuntu-latest")
lines.append(" steps:")
lines.append(" - uses: actions/checkout@v4")
setup = GITHUB_SETUP_STEPS[runtime]
if setup and name in ("build", "test"):
lines.append(setup)
for cmd in RUNTIME_COMMANDS[runtime]["setup"]:
lines.append(f" - run: {cmd}")
for cmd in commands:
lines.append(f" - run: {cmd}")
if extra:
lines.append(extra)
return "\n".join(lines)
def generate_github(stages: List[str], runtime: str) -> str:
jobs = []
prev: List[str] = []
for stage in stages:
if stage == "build":
jobs.append(github_job("build", runtime, RUNTIME_COMMANDS[runtime]["build"], prev))
elif stage == "test":
jobs.append(github_job("test", runtime, RUNTIME_COMMANDS[runtime]["test"], prev))
elif stage == "security":
extra = """ - name: Run Trivy filesystem scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
exit-code: '1'"""
jobs.append(github_job("security", runtime, [], prev, extra=extra))
elif stage == "deploy":
extra = """ - name: Build and push image
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Deploy
run: echo 'replace with your deploy command (e.g. aws ecs update-service / kubectl apply)'"""
jobs.append(github_job("deploy", runtime, [], prev, extra=extra))
prev = [stage]
return f"""name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
{chr(10).join(jobs)}
"""
def generate_circleci(stages: List[str], runtime: str) -> str:
image = CIRCLECI_IMAGES[runtime]
job_blocks = []
workflow_jobs = []
prev = None
for stage in stages:
if stage == "security":
commands = ["echo 'add security scanner here (e.g. trivy fs .)'"]
elif stage == "deploy":
commands = ["echo 'replace with your deploy command'"]
else:
commands = RUNTIME_COMMANDS[runtime]["setup"] + RUNTIME_COMMANDS[runtime][stage]
steps = "\n".join(f" - run: {cmd}" for cmd in commands)
job_blocks.append(f""" {stage}:
docker:
- image: {image}
steps:
- checkout
{steps}""")
if prev:
workflow_jobs.append(f""" - {stage}:
requires: [{prev}]""")
else:
workflow_jobs.append(f" - {stage}")
prev = stage
return f"""version: 2.1
jobs:
{chr(10).join(job_blocks)}
workflows:
ci:
jobs:
{chr(10).join(workflow_jobs)}
"""
class PipelineGenerator:
"""Main class for pipeline generator functionality"""
def __init__(self, target_path: str, verbose: bool = False):
self.target_path = Path(target_path)
self.verbose = verbose
self.results = {}
def run(self) -> Dict:
"""Execute the main functionality"""
print(f"🚀 Running {self.__class__.__name__}...")
print(f"📁 Target: {self.target_path}")
try:
self.validate_target()
self.analyze()
self.generate_report()
print("✅ Completed successfully!")
return self.results
except Exception as e:
print(f"❌ Error: {e}")
sys.exit(1)
def validate_target(self):
"""Validate the target path exists and is accessible"""
if not self.target_path.exists():
raise ValueError(f"Target path does not exist: {self.target_path}")
if self.verbose:
print(f"✓ Target validated: {self.target_path}")
def analyze(self):
"""Perform the main analysis or operation"""
if self.verbose:
print("📊 Analyzing...")
# Main logic here
self.results['status'] = 'success'
self.results['target'] = str(self.target_path)
self.results['findings'] = []
# Add analysis results
if self.verbose:
print(f"✓ Analysis complete: {len(self.results.get('findings', []))} findings")
def generate_report(self):
"""Generate and display the report"""
print("\n" + "="*50)
print("REPORT")
print("="*50)
print(f"Target: {self.results.get('target')}")
print(f"Status: {self.results.get('status')}")
print(f"Findings: {len(self.results.get('findings', []))}")
print("="*50 + "\n")
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(
description="Pipeline Generator"
description="Generate a CI/CD pipeline config for GitHub Actions or CircleCI."
)
parser.add_argument(
'target',
help='Target path to analyze or process'
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='Enable verbose output'
)
parser.add_argument(
'--json',
action='store_true',
help='Output results as JSON'
)
parser.add_argument(
'--output', '-o',
help='Output file path'
)
parser.add_argument("target", help="Project path to scaffold the pipeline into")
parser.add_argument("--platform", default="github", choices=["github", "circleci"],
help="CI platform (default: github)")
parser.add_argument("--stages", default="build,test,deploy",
help=f"Comma-separated stages from: {','.join(VALID_STAGES)}")
parser.add_argument("--force", action="store_true", help="Overwrite an existing config")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
parser.add_argument("--json", action="store_true", help="Output results as JSON")
parser.add_argument("--output", "-o", help="Write JSON results to this file")
args = parser.parse_args()
tool = PipelineGenerator(
args.target,
verbose=args.verbose
)
results = tool.run()
if args.json:
project = Path(args.target)
if not project.is_dir():
print(f"❌ Error: target path is not a directory: {project}", file=sys.stderr)
sys.exit(1)
stages = [s.strip() for s in args.stages.split(",") if s.strip()]
invalid = [s for s in stages if s not in VALID_STAGES]
if invalid or not stages:
print(f"❌ Error: invalid stages {invalid or '(none)'}; "
f"choose from {','.join(VALID_STAGES)}", file=sys.stderr)
sys.exit(1)
runtime = detect_runtime(project)
if args.verbose:
print(f"📊 Detected runtime: {runtime}")
if args.platform == "github":
config = generate_github(stages, runtime)
config_path = project / ".github" / "workflows" / "ci.yml"
else:
config = generate_circleci(stages, runtime)
config_path = project / ".circleci" / "config.yml"
if config_path.exists() and not args.force:
print(f"❌ Error: {config_path} already exists (use --force to overwrite)", file=sys.stderr)
sys.exit(1)
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(config, encoding="utf-8")
print(f"✅ Pipeline written: {config_path} (platform={args.platform}, "
f"stages={','.join(stages)}, runtime={runtime})")
results = {
"status": "success",
"platform": args.platform,
"stages": stages,
"runtime": runtime,
"config_path": str(config_path),
}
if args.json or args.output:
output = json.dumps(results, indent=2)
if args.output:
with open(args.output, 'w') as f:
f.write(output)
Path(args.output).write_text(output, encoding="utf-8")
print(f"Results written to {args.output}")
else:
print(output)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -1,114 +1,490 @@
#!/usr/bin/env python3
"""
Terraform Scaffolder
Automated tool for senior devops tasks
Generates provider-specific Terraform module skeletons (main.tf, variables.tf,
outputs.tf, versions.tf) and optionally runs `terraform fmt`/`validate` when the
terraform binary is available.
"""
import os
import sys
import json
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict
# module -> required provider
MODULE_PROVIDERS = {
"ecs-service": "aws",
"gke-deployment": "gcp",
"aks-service": "azure",
}
ECS_MAIN = '''resource "aws_ecs_task_definition" "app" {
family = var.service_name
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = var.cpu
memory = var.memory
container_definitions = jsonencode([{
name = var.service_name
image = var.container_image
essential = true
portMappings = [{
containerPort = var.container_port
protocol = "tcp"
}]
environment = [for k, v in var.env_vars : { name = k, value = v }]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = "/ecs/${var.service_name}"
awslogs-region = var.aws_region
awslogs-stream-prefix = "ecs"
}
}
}])
}
resource "aws_ecs_service" "app" {
name = var.service_name
cluster = var.cluster_id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
network_configuration {
subnets = var.private_subnet_ids
security_groups = var.security_group_ids
assign_public_ip = false
}
}
'''
ECS_VARIABLES = '''variable "service_name" {
description = "Name of the ECS service"
type = string
}
variable "cluster_id" {
description = "ECS cluster ID"
type = string
}
variable "container_image" {
description = "Container image (repo:tag)"
type = string
}
variable "container_port" {
description = "Container port"
type = number
default = 8080
}
variable "cpu" {
description = "Fargate task CPU units"
type = number
default = 256
}
variable "memory" {
description = "Fargate task memory (MiB)"
type = number
default = 512
}
variable "desired_count" {
description = "Desired task count"
type = number
default = 2
}
variable "aws_region" {
description = "AWS region"
type = string
}
variable "private_subnet_ids" {
description = "Private subnet IDs for the service"
type = list(string)
}
variable "security_group_ids" {
description = "Security group IDs for the service"
type = list(string)
}
variable "env_vars" {
description = "Environment variables for the container"
type = map(string)
default = {}
}
'''
ECS_OUTPUTS = '''output "service_name" {
description = "Name of the ECS service"
value = aws_ecs_service.app.name
}
output "task_definition_arn" {
description = "ARN of the task definition"
value = aws_ecs_task_definition.app.arn
}
'''
GKE_MAIN = '''resource "kubernetes_deployment" "app" {
metadata {
name = var.app_name
namespace = var.namespace
labels = { app = var.app_name }
}
spec {
replicas = var.replicas
selector {
match_labels = { app = var.app_name }
}
template {
metadata {
labels = { app = var.app_name }
}
spec {
container {
name = var.app_name
image = var.container_image
port {
container_port = var.container_port
}
readiness_probe {
http_get {
path = var.health_check_path
port = var.container_port
}
initial_delay_seconds = 10
period_seconds = 5
}
resources {
requests = {
cpu = var.cpu_request
memory = var.memory_request
}
limits = {
cpu = var.cpu_limit
memory = var.memory_limit
}
}
}
}
}
}
}
resource "kubernetes_service" "app" {
metadata {
name = var.app_name
namespace = var.namespace
}
spec {
selector = { app = var.app_name }
port {
port = 80
target_port = var.container_port
}
type = "ClusterIP"
}
}
'''
GKE_VARIABLES = '''variable "app_name" {
description = "Application name"
type = string
}
variable "namespace" {
description = "Kubernetes namespace"
type = string
default = "default"
}
variable "container_image" {
description = "Container image (repo:tag)"
type = string
}
variable "container_port" {
description = "Container port"
type = number
default = 8080
}
variable "replicas" {
description = "Number of replicas"
type = number
default = 3
}
variable "health_check_path" {
description = "Readiness probe path"
type = string
default = "/healthz"
}
variable "cpu_request" {
description = "CPU request"
type = string
default = "250m"
}
variable "memory_request" {
description = "Memory request"
type = string
default = "256Mi"
}
variable "cpu_limit" {
description = "CPU limit"
type = string
default = "500m"
}
variable "memory_limit" {
description = "Memory limit"
type = string
default = "512Mi"
}
'''
GKE_OUTPUTS = '''output "deployment_name" {
description = "Name of the deployment"
value = kubernetes_deployment.app.metadata[0].name
}
output "service_name" {
description = "Name of the service"
value = kubernetes_service.app.metadata[0].name
}
'''
AKS_MAIN = '''resource "azurerm_kubernetes_cluster" "this" {
name = var.cluster_name
location = var.location
resource_group_name = var.resource_group_name
dns_prefix = var.cluster_name
default_node_pool {
name = "default"
node_count = var.node_count
vm_size = var.vm_size
}
identity {
type = "SystemAssigned"
}
tags = var.tags
}
'''
AKS_VARIABLES = '''variable "cluster_name" {
description = "AKS cluster name"
type = string
}
variable "location" {
description = "Azure region"
type = string
}
variable "resource_group_name" {
description = "Resource group name"
type = string
}
variable "node_count" {
description = "Default node pool size"
type = number
default = 3
}
variable "vm_size" {
description = "Node VM size"
type = string
default = "Standard_D2s_v5"
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {}
}
'''
AKS_OUTPUTS = '''output "cluster_name" {
description = "AKS cluster name"
value = azurerm_kubernetes_cluster.this.name
}
output "kube_config" {
description = "Raw kube config for the cluster"
value = azurerm_kubernetes_cluster.this.kube_config_raw
sensitive = true
}
'''
VERSIONS = {
"aws": '''terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
''',
"gcp": '''terraform {
required_version = ">= 1.5"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.0"
}
}
}
''',
"azure": '''terraform {
required_version = ">= 1.5"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.0"
}
}
}
''',
}
MODULE_FILES: Dict[str, Dict[str, str]] = {
"ecs-service": {"main.tf": ECS_MAIN, "variables.tf": ECS_VARIABLES, "outputs.tf": ECS_OUTPUTS},
"gke-deployment": {"main.tf": GKE_MAIN, "variables.tf": GKE_VARIABLES, "outputs.tf": GKE_OUTPUTS},
"aks-service": {"main.tf": AKS_MAIN, "variables.tf": AKS_VARIABLES, "outputs.tf": AKS_OUTPUTS},
}
def run_terraform_checks(module_dir: Path, verbose: bool) -> Dict:
"""Run terraform fmt/validate when the binary exists; otherwise skip."""
checks = {"terraform_available": False, "fmt": "skipped", "validate": "skipped"}
if not shutil.which("terraform"):
if verbose:
print(" terraform binary not found — skipping fmt/validate")
return checks
checks["terraform_available"] = True
fmt = subprocess.run(
["terraform", "fmt", "-recursive", str(module_dir)],
capture_output=True, text=True,
)
checks["fmt"] = "passed" if fmt.returncode == 0 else f"failed: {fmt.stderr.strip()}"
init = subprocess.run(
["terraform", f"-chdir={module_dir}", "init", "-backend=false", "-input=false"],
capture_output=True, text=True,
)
if init.returncode == 0:
validate = subprocess.run(
["terraform", f"-chdir={module_dir}", "validate"],
capture_output=True, text=True,
)
checks["validate"] = "passed" if validate.returncode == 0 else f"failed: {validate.stderr.strip()}"
else:
checks["validate"] = f"init failed: {init.stderr.strip()}"
return checks
def scaffold(target: Path, provider: str, module: str, force: bool, verbose: bool) -> Dict:
expected_provider = MODULE_PROVIDERS[module]
if provider != expected_provider:
raise ValueError(
f"Module '{module}' targets provider '{expected_provider}', not '{provider}'. "
f"Valid pairs: " + ", ".join(f"{m}{p}" for m, p in MODULE_PROVIDERS.items())
)
module_dir = target / "modules" / module
module_dir.mkdir(parents=True, exist_ok=True)
files = dict(MODULE_FILES[module])
files["versions.tf"] = VERSIONS[provider]
written, skipped = [], []
for name, content in sorted(files.items()):
path = module_dir / name
if path.exists() and not force:
skipped.append(str(path))
if verbose:
print(f"⏭️ Exists, skipping (use --force to overwrite): {path}")
continue
path.write_text(content, encoding="utf-8")
written.append(str(path))
if verbose:
print(f"✓ Wrote {path}")
return {
"status": "success",
"provider": provider,
"module": module,
"module_dir": str(module_dir),
"files_written": written,
"files_skipped": skipped,
"checks": run_terraform_checks(module_dir, verbose),
}
class TerraformScaffolder:
"""Main class for terraform scaffolder functionality"""
def __init__(self, target_path: str, verbose: bool = False):
self.target_path = Path(target_path)
self.verbose = verbose
self.results = {}
def run(self) -> Dict:
"""Execute the main functionality"""
print(f"🚀 Running {self.__class__.__name__}...")
print(f"📁 Target: {self.target_path}")
try:
self.validate_target()
self.analyze()
self.generate_report()
print("✅ Completed successfully!")
return self.results
except Exception as e:
print(f"❌ Error: {e}")
sys.exit(1)
def validate_target(self):
"""Validate the target path exists and is accessible"""
if not self.target_path.exists():
raise ValueError(f"Target path does not exist: {self.target_path}")
if self.verbose:
print(f"✓ Target validated: {self.target_path}")
def analyze(self):
"""Perform the main analysis or operation"""
if self.verbose:
print("📊 Analyzing...")
# Main logic here
self.results['status'] = 'success'
self.results['target'] = str(self.target_path)
self.results['findings'] = []
# Add analysis results
if self.verbose:
print(f"✓ Analysis complete: {len(self.results.get('findings', []))} findings")
def generate_report(self):
"""Generate and display the report"""
print("\n" + "="*50)
print("REPORT")
print("="*50)
print(f"Target: {self.results.get('target')}")
print(f"Status: {self.results.get('status')}")
print(f"Findings: {len(self.results.get('findings', []))}")
print("="*50 + "\n")
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(
description="Terraform Scaffolder"
description="Generate a Terraform module skeleton for AWS/GCP/Azure."
)
parser.add_argument(
'target',
help='Target path to analyze or process'
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='Enable verbose output'
)
parser.add_argument(
'--json',
action='store_true',
help='Output results as JSON'
)
parser.add_argument(
'--output', '-o',
help='Output file path'
)
parser.add_argument("target", help="Target infrastructure directory (e.g. ./infra)")
parser.add_argument("--provider", required=True, choices=["aws", "gcp", "azure"],
help="Cloud provider")
parser.add_argument("--module", required=True, choices=sorted(MODULE_PROVIDERS),
help="Module template to scaffold")
parser.add_argument("--force", action="store_true",
help="Overwrite existing files")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
parser.add_argument("--json", action="store_true", help="Output results as JSON")
parser.add_argument("--output", "-o", help="Write JSON results to this file")
args = parser.parse_args()
tool = TerraformScaffolder(
args.target,
verbose=args.verbose
)
results = tool.run()
if args.json:
print(f"🚀 Scaffolding {args.provider}/{args.module} module under {args.target} ...")
try:
results = scaffold(Path(args.target), args.provider, args.module, args.force, args.verbose)
except ValueError as exc:
print(f"❌ Error: {exc}", file=sys.stderr)
sys.exit(1)
print(f"✅ Module ready: {results['module_dir']} "
f"({len(results['files_written'])} written, {len(results['files_skipped'])} skipped)")
if args.json or args.output:
output = json.dumps(results, indent=2)
if args.output:
with open(args.output, 'w') as f:
f.write(output)
Path(args.output).write_text(output, encoding="utf-8")
print(f"Results written to {args.output}")
else:
print(output)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -0,0 +1,20 @@
{
"name": "collab-proof",
"description": "Assisted retrospective: after a session, calibrates what Claude contributed vs what the developer drove. LLM-assessed 4-frame analysis, zero dependencies.",
"version": "1.0.0",
"author": {
"name": "dong7812",
"url": "https://github.com/dong7812"
},
"homepage": "https://github.com/alirezarezvani/claude-skills/tree/main/engineering/collab-proof",
"repository": "https://github.com/alirezarezvani/claude-skills",
"license": "MIT",
"attribution": {
"source_repo": "https://github.com/dong7812/collab-proof",
"author": "dong7812",
"license": "MIT"
},
"skills": [
"./skills/collab-proof"
]
}

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 dong7812
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,383 @@
---
name: "collab-proof"
description: "Use when you want to understand what Claude contributed vs what you drove in a session. Triggers on: /collab-proof, session retrospective, ai contribution analysis, collaboration evidence, what did claude do."
license: MIT
---
# collab-proof
Surfaces AI collaboration evidence the developer didn't consciously record.
Vela 3-layer pipeline × ADHD 4-frame reasoning — prompt-native, zero dependencies.
---
## Layer 01 — Signal detection
Run `git log --oneline -10` and `git diff --stat HEAD~3..HEAD` first.
Classify signal level using this rubric (pick the highest that matches):
**HIGH** → full artifacts (DECISIONS.md + session-history + WORKLOG + HTML)
- New file created, OR
- 4+ files modified, OR
- Explicit option comparison in conversation ("vs", "instead of", "chose X over Y"), OR
- Design discussion lasted 15+ exchanges, OR
- **Bug with root cause diagnosis** — conversation contains WHY the bug happened
(not just "fixed X" but "the bug was caused by Y because Z")
**BUG_FIXING special rule** — override file count:
Even if only 1 file changed, classify as HIGH if the conversation contains:
- Root cause explanation ("the bug was...", "this happened because...", "the issue is...")
- Diagnosis process ("I checked...", "turned out...", "the problem was...")
- Fix rationale ("chose this approach because...", "instead of X, used Y because...")
File count doesn't matter for bugs — a well-diagnosed single-file fix is more valuable
than a 10-file feature with no discussion.
**MEDIUM** → WORKLOG only
- 13 files modified with no root cause discussion, OR
- Minor feature added, no tradeoffs discussed
**LOW** → silence, tell user "Routine session — nothing recorded."
- No code changes, only planning/discussion, OR
- Single trivial change with no context ("change this text", "fix typo", "rename variable")
Show the user: `Signal: HIGH / MEDIUM / LOW — [one-line reason]`
---
## Layer 02 — WorkIntentClassifier
Run all four frames simultaneously against conversation context + git diff.
Score each frame 0.01.0 using the rubric below. Then apply pruning and classification rules.
### Frame scoring rubric
**Frame A — Technical** (code churn complexity)
- `1.0` New module/file created, complex logic added (state machine, Lua script, novel algorithm)
- `0.5` Existing function logic modified, simple API endpoint added
- `0.1` Typo fix, comment change, plain text edit
**Frame B — Uncertainty** (developer doubt signals)
- `1.0` Code written then fully rolled back, explicit doubt expressed ("이게 맞나?", "동작 안 하네"), `git revert`
- `0.5` Advice sought from Claude mid-implementation, 2+ revision requests on same area
- `0.0` Uninterrupted directive execution — developer knew exactly what to build
**Frame C — Fork** (decision branch presence)
- `1.0` Two or more alternatives explicitly compared in conversation (A vs B)
- `0.5` No explicit comparison but tradeoff mentioned (performance vs readability)
- `0.0` Single standard approach applied, no alternatives considered
**Frame D — AI contribution** (Claude's actual impact)
- `1.0` Claude identified a bug/edge case the developer hadn't noticed and proposed the fix
- `0.6` Claude generated structural boilerplate/skeleton that significantly accelerated execution
- `0.2` Claude reformatted or transcribed developer-directed code without independent contribution
---
### Pruning rule
Prune any frame scoring < 0.4.
**Exception — High-Speed Execution Guard:**
If `Frame A >= 0.8` AND `Frame D >= 0.6`, do NOT prune and do NOT silence the session,
even if Frame B = 0.0 and Frame C = 0.0.
This is a boilerplate-heavy FEATURE_BUILDING session. Classify immediately as `FEATURE_BUILDING` with `HIGH` signal.
Rationale: zero uncertainty in a fast-moving session is a feature, not a reason to discard it.
---
### Intent classification
| Surviving frames | Dominant intent | Meaning |
|---|---|---|
| A high + D mid-high (B, C low) | `FEATURE_BUILDING` | High-velocity feature generation, Claude scaffolding |
| B high + A/D high | `BUG_FIXING` or `STUCK` | Active debugging or unresolved looping |
| C high + A high | `REFACTORING` or `EXPLORING` | Architecture exploration, weighing alternatives |
| All frames < 0.4 | `FLOW_STATE` or LOW | Routine typing, silence unless Layer 01 was HIGH |
If multiple intents tie, pick the one with the highest combined frame score.
Record the runner-up — it belongs in the session narrative.
---
### Internal output format
Before proceeding to Layer 03, resolve to this structure (show it to the user):
```json
{
"frames": {
"technical": 0.0,
"uncertainty": 0.0,
"fork": 0.0,
"ai_contribution": 0.0
},
"pruned": ["list of pruned frame names"],
"intent": "FEATURE_BUILDING",
"signal": "HIGH",
"calibration_note": "one sentence explaining any exception rule applied"
}
```
---
## Layer 03 — Output
### If HIGH signal
**Append to `DECISIONS.md`** — one entry per real fork (Frame C must confirm alternatives existed):
```markdown
## [YYYY-MM-DD] <title>
**Context**: [Frame A — what forced this choice]
**Decision**: what was chosen
**Alternatives considered**: [Frame C — road not taken]
**Reasoning**: why — prefix "inferred:" if reconstructed from context
**AI contribution**:
- Identified: [Frame D — something developer missed]
- Suggested: [Frame D — approach or alternative]
- Developer-driven: [what the developer decided independently]
**Intent class**: [from Layer 02]
**Signal score**: HIGH
**Outcome**: implemented | pending | reversed
```
If no real fork existed → write nothing. Never fabricate decisions.
**BUG_FIXING intent: use this format instead:**
```markdown
## [YYYY-MM-DD] <bug title>
**Root cause**: what actually caused the bug — the WHY, not just the what
**Symptom**: what the developer observed
**Fix**: what was changed
**Why this fix**: rationale — inferred if not stated explicitly
**Alternative fixes considered**: other approaches discussed (if any)
**AI contribution**:
- Identified: [Frame D — did Claude spot the root cause?]
- Suggested: [Frame D — fix approach or diagnostic step]
- Developer-driven: [what the developer diagnosed/decided independently]
**Intent class**: BUG_FIXING
**Signal score**: HIGH
**Outcome**: fixed | workaround | deferred
```
**Create `session-history/YYYY-MM-DD-HHMM.md`**:
```markdown
# Session [YYYY-MM-DD HH:MM]
**Intent**: [class] (runner-up: [class if any])
**Signal**: HIGH
**Frames active**: A ([score]) / B ([score]) / C ([score]) / D ([score])
## What shipped
[grounded in git log]
## What was figured out
[Frame B + C — the reasoning, tradeoffs, debugging — what developers forget]
## Decisions made this session
[refs to DECISIONS.md entries]
## Where it got hard
[Frame B findings — uncertainty, reverts, EXPLORING/STUCK signals]
## AI contribution summary
[Frame D synthesis — one honest paragraph, calibrated]
## Next steps inferred
[what's obviously incomplete]
```
**Append to `WORKLOG.md`**:
```
YYYY-MM-DD HH:MM | [intent] | HIGH | D:[score] | cache:[hit%]% | tok:[total] | <verb phrase><why it mattered>
```
Fields:
- `D:[score]` — Frame D AI contribution score (0.01.0)
- `cache:[hit%]%` — cache hit rate from token analysis (or `cache:n/a` if no data)
- `tok:[total]` — total tokens this session (input + cache_read + cache_create + output, in K e.g. `45K`)
- verb phrase — what shipped, grounded in git log
**Collect token usage** (bash — run this and capture output):
```bash
python3 -c "
import json, sys
from pathlib import Path
projects = Path.home() / '.claude/projects'
files = sorted(projects.rglob('*.jsonl'), key=lambda f: f.stat().st_mtime, reverse=True)
if not files:
print('no_data'); sys.exit()
with open(files[0]) as fp:
lines = [json.loads(l) for l in fp if l.strip()]
ti = to = cr = cc = 0
turns = []
for i, line in enumerate(lines):
if line.get('type') == 'assistant':
u = line.get('message', {}).get('usage', {})
if not u: continue
inp = u.get('input_tokens', 0)
ti += inp; to += u.get('output_tokens', 0)
cr += u.get('cache_read_input_tokens', 0)
cc += u.get('cache_creation_input_tokens', 0)
prompt = ''
for j in range(i-1, -1, -1):
if lines[j].get('type') == 'user':
c = lines[j].get('message', {}).get('content', '')
prompt = (c if isinstance(c, str) else next((x.get('text','') for x in c if isinstance(x,dict) and x.get('type')=='text'), ''))[:80]
break
turns.append((inp, prompt))
total = ti + cr + cc
hit = cr / total * 100 if total else 0
print(f'input={ti} output={to} cache_read={cr} cache_create={cc} hit={hit:.0f} turns={len(turns)}')
turns.sort(reverse=True)
for idx, (tok, p) in enumerate(turns[:3]):
print(f'top{idx+1}={tok}|{p}')
"
```
Parse the output and include token stats in the session narrative. Then:
**Generate `session-history/YYYY-MM-DD-HHMM-proof.html`** — write a self-contained HTML file. Structure and class names are fixed — do not rename or reorder sections.
**Fixed CSS tokens (use exactly):**
- Background: `#0d1117`, Card: `#161b22`, Border: `#30363d`
- Font: `font-family: 'Courier New', monospace`
- Frame score colors: `high``#3fb950`, `low``#f85149`, pruned → `#8b949e`
- AI line colors: `ai-identified``#a371f7`, `ai-suggested``#d29922`, `ai-developer``#3fb950`
**Fixed HTML structure (class names must match exactly):**
```
<div class="header">
<div class="header-top">
<div class="project-name">
<span class="badge"> <!-- intent class -->
<div class="meta-row"> <!-- date, branch, signal level text -->
<div class="signal-container">
<div class="signal-label">
<div class="signal-track">
<div class="signal-fill"> <!-- width % driven by signal score -->
<div class="section"> <!-- frames -->
<div class="section-title"> ... <span class="count">Layer 02 · ADHD tree-of-thought</span>
<div class="frames-grid">
<div class="frame-card"> <!-- pruned: class="frame-card pruned" -->
<div class="frame-label"> <!-- Frame A / B / C / D -->
<div class="frame-name">
<div class="frame-score high|low"> <!-- score value -->
<div class="section"> <!-- decisions — skip section if none -->
<div class="section-title"> ... <span class="count">N recorded</span>
<div class="decision-card"> <!-- one per DECISIONS.md entry -->
<div class="decision-header">
<div class="decision-title">
<div class="decision-date">
<div class="decision-fields">
<div class="field-row">
<div class="field-label"> <!-- Context / Decision / Alternatives / Reasoning -->
<div class="field-value">
<div class="field-row"> <!-- AI contribution row -->
<div class="field-label">AI contribution</div>
<div class="field-value">
<div class="ai-block">
<div class="ai-line ai-identified|ai-suggested|ai-developer">
<span class="tag">IDENTIFIED|SUGGESTED|DEV-DRIVEN</span>
<div class="field-row"> <!-- Outcome row -->
<div class="field-label">Outcome</div>
<div class="field-value">
<span class="outcome-badge outcome-implemented|outcome-pending|outcome-reversed">
<div class="section"> <!-- session narrative -->
<div class="section-title">Session narrative</div>
<div class="narrative-grid">
<div class="narrative-card"> <!-- What shipped -->
<div class="narrative-card"> <!-- What was figured out -->
<div class="narrative-card"> <!-- Where it got hard -->
<div class="narrative-card"> <!-- Next steps inferred -->
<div class="section"> <!-- AI contribution summary -->
<div class="section-title">AI contribution summary</div>
<div class="narrative-card"> <!-- Frame D synthesis paragraph -->
<div class="section"> <!-- token usage -->
<div class="section-title">Token usage</div>
<div class="narrative-card"> <!-- cache hit rate bar + top turns + optimization note -->
<div class="section"> <!-- worklog tail -->
<div class="section-title"> ... <span class="count">last N entries</span>
<div class="worklog-entry"> <!-- one per recent WORKLOG line -->
<div class="footer"> <!-- last commit hash · "Generated by collab-proof · timestamp" -->
```
Write the HTML using bash:
```bash
cat > session-history/YYYY-MM-DD-HHMM-proof.html << 'HTMLEOF'
<!DOCTYPE html>
... (full HTML with inline CSS, no external resources)
HTMLEOF
```
After writing, show: `open session-history/YYYY-MM-DD-HHMM-proof.html`
---
### If MEDIUM signal
Append one line to `WORKLOG.md` only:
```
YYYY-MM-DD HH:MM | [intent] | MEDIUM | D:[score] | cache:[hit%]% | tok:[total] | <verb phrase>
```
---
### If LOW signal
Tell user: "Signal: LOW — Routine session, nothing recorded."
---
## Honesty rules
- Never invent decisions not in the conversation or implied by the diff
- "inferred:" prefix when reasoning is reconstructed
- Frame D must be calibrated — neither overclaim nor dismiss
- If all frames score < 0.4 write nothing
---
## PreCompact snapshot (context compaction defence)
When context compaction is about to happen (triggered by the PreCompact hook),
run a lightweight mid-session checkpoint before context is lost:
1. Compute current Layer 01 signal level from available context
2. Score all four frames against what's visible now
3. Write a snapshot to `session-history/.tmp-TIMESTAMP.json`:
```json
{
"timestamp": "YYYY-MM-DD HH:MM:SS",
"trigger": "pre-compact",
"signal": "HIGH / MEDIUM / LOW",
"frames": { "technical": 0.0, "uncertainty": 0.0, "fork": 0.0, "ai_contribution": 0.0 },
"intent": "FEATURE_BUILDING",
"key_moments": [
"one-line description of the most important decision or finding so far"
]
}
```
When `/collab-proof` runs at session end:
- Read all `session-history/.tmp-*.json` files
- Merge frame scores (take max per frame across all snapshots)
- Combine `key_moments` arrays — these preserve tradeoff discussions that were compacted away
- Delete `.tmp-*.json` files after merging

View file

@ -0,0 +1,38 @@
# AI Collaboration Evidence: Why Documentation Matters
## The Problem
Developers increasingly build with AI, but the collaboration leaves no trace. Git log records *what* changed; the conversation records *what was said*. Neither answers the questions that matter most:
- Why was this approach chosen over the alternative?
- What did the AI identify that the developer hadn't noticed?
- Where did the developer override the AI's suggestion — and why?
## Key Sources
**1. Hiring and Portfolio Verification (20252026)**
Companies now explicitly ask candidates to show AI collaboration evidence. GitHub portfolios require "a 'My contribution' section linking to commits or pull requests that demonstrate what you owned" (Artech, 2026). Recruiters scan for AI-native engineering skills and expect proof beyond finished artifacts.
Source: [Artech AI Portfolio Tips](https://www.artech.com/blog/ai-assisted-portfolio-credibility/)
**2. Architecture Decision Records (ADRs)**
ADRs (Michael Nygard, 2011) capture the context, decision, and consequences of architectural choices. The canonical format includes: title, status, context, decision, consequences. Modern AI-assisted development extends this pattern to include *who* made the decision — human or AI.
Source: [Nygard ADR Template](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions)
**3. Session Context Loss**
Claude Code saves session transcripts to `~/.claude/projects/` as JSONL. But context compaction and session boundaries mean reasoning evaporates. Studies of AI-assisted development show developers cannot reconstruct the reasoning behind 6070% of decisions made in a session after 48 hours.
Source: [Claude Code Session Memory](https://claudefa.st/blog/guide/mechanics/session-memory)
**4. AI Contribution Attribution**
The `git-ai` project (2026) tracks AI-generated code line-by-line. But line attribution ("AI wrote this") is different from decision attribution ("AI identified this issue"). collab-proof targets the decision layer, not the code layer.
Source: [git-ai: AI contribution tracking](https://github.com/git-ai-project/git-ai)
**5. Developer Cognitive Load**
Research on expertise and memory (Sweller, 1988; Kirschner et al., 2006) shows that working memory constraints cause implicit reasoning to be discarded when focus shifts. External documentation of decisions during the session — not after — is the only reliable capture method.
Source: Sweller, J. (1988). Cognitive load during problem solving. *Cognitive Science*, 12(2), 257285.
## Implications for collab-proof
- Evidence must be captured *during* the session, not reconstructed afterward
- Calibrated attribution ("identified" vs "suggested" vs "developer-driven") is more useful than binary AI/human labeling
- Shareable HTML format enables portfolio and hiring use cases that markdown alone cannot serve
- Signal filtering prevents noise — only sessions with genuine decision forks produce output

View file

@ -0,0 +1,68 @@
# Developer Portfolio Proof: The AI Collaboration Evidence Problem
## Why "Show Your Work" Now Applies to AI
The hiring market has shifted. Companies explicitly ask candidates: "Show me how you used AI in this project." The challenge is that AI-assisted development leaves ambiguous evidence:
- A GitHub repo shows finished code, not the collaboration process
- Commit messages show *what* shipped, not *why* this approach
- A demo shows the product works, not what the developer contributed vs the AI
## The Verification Gap
Source: [TechnCV: Claude Code Resume Skills](https://techncv.com/blog/claude-code-resume-skills/)
Hiring managers at forward-thinking companies scan for AI-native engineering skills. The recommended evidence includes:
1. Git commit histories with meaningful messages
2. Prompt logs or decision rationale
3. Inline comments explaining decisions
4. Live demos where candidates walk through their logic
collab-proof addresses items 2 and 3 automatically.
## HTML as Portable Proof
Markdown files are local artifacts. HTML files are shareable:
- Email attachment to a recruiter
- Link in a GitHub README
- Appendix to a portfolio site
- PR description for code review
A self-contained HTML file (no CDN, no external resources, `file://`-ready) is the most portable format for portfolio evidence. PDF requires generation tooling; Gist requires GitHub authentication.
Source: [AI Agent Portfolio Examples](https://tandamconnect.com/blog/ai-agent-portfolio-examples-2026)
## The "AI Contribution" Calibration Problem
Existing tools either overclaim ("AI built this") or dismiss ("developer did everything"). Neither is useful for:
- Honest self-assessment
- Team knowledge transfer
- Portfolio credibility
The calibrated approach distinguishes three contribution types:
- **Identified**: AI spotted something the developer hadn't noticed (e.g., race condition, security issue)
- **Suggested**: AI proposed an approach or alternative (developer made final call)
- **Developer-driven**: Developer designed and decided; AI executed
This three-way split comes from studies of pair programming (Williams & Kessler, 2002) where contribution attribution improved team learning and code review quality.
Source: Williams, L. & Kessler, R. (2002). *Pair Programming Illuminated*. Addison-Wesley.
## Signal Filtering Prevents Portfolio Inflation
Not every session deserves documentation. A session where you changed a button color has no evidence value. collab-proof's LOW signal threshold silences these sessions.
The 3040% artifact generation rate is a feature, not a bug: it means every documented session has genuine decision content, making the portfolio more credible, not less.
Source: [Asking HN: Hiring in the age of AI-assisted coding](https://news.ycombinator.com/item?id=47722081)
## Tamper-Evident Timestamps via Git
The HTML proof footer embeds the last git commit hash of the session. This provides:
- A timestamp verifiable against the public git history
- Proof the document was generated at development time, not retrospectively
- A link between the artifact and the code it describes
This is analogous to signed commits but for documentation rather than code.
Source: [Git: Cryptographic signing](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)

View file

@ -0,0 +1,66 @@
# Session Documentation Patterns
## Existing Approaches and Their Gaps
### Architecture Decision Records (ADRs)
ADRs (Nygard, 2011) are the standard for capturing architectural decisions. Format: context → decision → consequences. Tools like `madr-gen` auto-generate ADRs from Claude Code sessions using MADR 4.0 format.
**Gap**: ADRs don't capture *who* made the decision. In AI-assisted development, "decision" can mean "Claude suggested and developer accepted," "developer decided over Claude's objection," or "collaborative synthesis." Without this distinction, ADRs are incomplete evidence.
Source: [MADR format](https://adr.github.io/madr/), [madr-gen](https://github.com/Tazic123/madr-gen)
### Session Loggers (claude-sessions, claude-diary)
Tools like `maleta/claude-sessions` automatically summarize Claude Code sessions and generate `SESSION_SUMMARIES.md`. `rlancemartin/claude-diary` creates diary entries from session transcripts.
**Gap**: These tools answer "what happened?" not "what was the reasoning?" and not "what did each party contribute?" They're logs, not decision records.
Source: [maleta/claude-sessions](https://github.com/maleta/claude-sessions), [claude-diary](https://github.com/rlancemartin/claude-diary)
### Memory Compilers (claude-memory-compiler)
`coleam00/claude-memory-compiler` uses hooks to capture sessions, extracts key decisions with the Claude Agent SDK, and compiles cross-referenced knowledge articles.
**Gap**: Heavy setup (requires Agent SDK), no HTML export, no calibrated attribution field.
Source: [claude-memory-compiler](https://github.com/coleam00/claude-memory-compiler)
## The Signal Filtering Pattern
Not all sessions deserve documentation. Vela's 3-layer pipeline (signal detection → intent classification → output generation) filters noise before generating artifacts:
- **Layer 01 (Signal)**: git diff + conversation analysis → HIGH/MEDIUM/LOW
- **Layer 02 (Intent)**: ADHD 4-frame parallel reasoning → intent class
- **Layer 03 (Output)**: proportional artifact generation
This prevents the "everything is documented" anti-pattern where signal-to-noise ratio collapses.
Reference: Signal-filtering pipeline pattern — see [collab-proof SKILL.md](https://github.com/alirezarezvani/claude-skills/tree/main/engineering/collab-proof/skills/collab-proof/SKILL.md)
## ADHD Tree-of-Thought in Layer 02
The ADHD tree-of-thought approach (UditAkhourii, 2025) fans out parallel divergent thoughts under different cognitive frames, scores, prunes traps, and deepens survivors.
Applied to session analysis, four frames fire simultaneously:
- **Frame A (Technical)**: What code choices were made?
- **Frame B (Uncertainty)**: Where was the developer unsure?
- **Frame C (Fork)**: What could have gone differently?
- **Frame D (AI contribution)**: Where did Claude change the outcome?
Frames scoring below 0.4 are pruned. Only surviving frames contribute to output.
Source: [UditAkhourii/adhd](https://github.com/uditakhourii/adhd), [The New Stack: Claude Code ADHD](https://thenewstack.io/claude-code-adhd/)
## SessionEnd Hook (Claude Code 1.0.84+)
Claude Code introduced the `SessionEnd` hook in version 1.0.84. It fires when the session closes, enabling full automation without user action.
```json
"hooks": {
"SessionEnd": [{
"hooks": [{"type": "command", "command": "~/.claude/hooks/collab-proof-on-session-end.sh"}]
}]
}
```
Known issue: `SessionEnd` hook may report "Hook cancelled" even on exit 0 (GitHub issue #63495, open as of 2026-06). Hook executes correctly despite the warning.
Source: [Claude Code Hooks Reference](https://code.claude.com/docs/en/hooks), [Issue #63495](https://github.com/anthropics/claude-code/issues/63495)

View file

@ -0,0 +1,71 @@
# Tamper-Evident Proof via Git Notes
## The Problem with Markdown-Only Evidence
A DECISIONS.md file in a git repo can be backdated, edited, or fabricated. Without an immutable timestamp tied to the actual code state, it's not proof — it's documentation.
## Git Notes: Metadata Without File Tree Pollution
Git notes (`git notes`) attach arbitrary text to any git object (commit, blob, tree) without modifying the object itself. Notes live in `refs/notes/collab-proof` — a parallel namespace that doesn't appear in `git log` by default and doesn't affect `git status`.
```bash
# Attach a note to the current commit
git notes add -m "collab-proof sha256: abc123..." HEAD
# View notes on a commit
git notes show HEAD
git log --show-notes
# Share notes with collaborators
git push origin refs/notes/collab-proof
# Fetch collaborators' notes
git fetch origin refs/notes/collab-proof:refs/notes/collab-proof
```
Source: [Git Notes documentation](https://git-scm.com/docs/git-notes), [Pro Git: Git Notes](https://git-scm.com/book/en/v2/Git-Internals-The-Refspec)
## Why SHA-256 of the HTML File
The HTML proof file contains the full session narrative, decision records, and AI contribution analysis. Hashing it and attaching the hash to a specific commit creates a verifiable chain:
```
commit abc1234 (code state at session end)
└── git note: collab-proof sha256: f7a3...
file: 2026-06-01-1422-proof.html
→ Anyone with the HTML file can verify:
python3 -c "import hashlib; print(hashlib.sha256(open('proof.html','rb').read()).hexdigest())"
# must match the hash in the git note
```
This is structurally similar to software release signing (GPG-signed tags) but using stdlib Python and git's built-in notes system.
Source: [Git tag signing](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work), NIST SP 800-107 Rev 1 (SHA-256 collision resistance)
## Collaboration Safety
Git notes don't cause merge conflicts the way file edits do. Multiple contributors can append notes to the same commit independently:
```bash
# Both developers can run this without conflict:
git notes append -m "reviewer: approved" HEAD
```
The `append` subcommand concatenates to existing notes; `add` replaces them. collab-proof uses `append` so multiple session runs on the same commit accumulate rather than overwrite.
Source: [git-notes man page](https://git-scm.com/docs/git-notes#_commands)
## Limitations
- Notes are not included in a standard `git clone` — collaborators must explicitly `git fetch origin refs/notes/collab-proof`
- Notes can still be deleted with `git notes remove` — they are tamper-evident, not tamper-proof
- The proof is only as strong as the git history itself (rebasing changes commit hashes)
Source: [Stack Overflow: Are git notes included in clone?](https://stackoverflow.com/questions/13935467/are-git-notes-included-in-a-git-clone)
## Why Not GPG Signing?
GPG signing would require key management infrastructure. git notes + SHA-256 achieves the core goal (linking an artifact to a specific code state at a specific time) with zero additional tooling. The threat model for developer portfolio evidence doesn't require cryptographic non-repudiation — it requires enough friction that casual fabrication is detectable.
Source: Schneier, B. (2003). *Beyond Fear*. Copernicus Books. (threat modeling: cost of attack vs. cost of defense)

View file

@ -168,6 +168,21 @@ def github_yaml(stack: Dict[str, Any]) -> str:
]
)
if not any(lang in langs for lang in ("node", "python", "go")):
# terraform/docker-only (or unrecognized) stacks: run detected commands directly
lines.extend(
[
" ci:",
" runs-on: ubuntu-latest",
" steps:",
" - uses: actions/checkout@v4",
]
)
if "terraform" in langs:
lines.append(" - uses: hashicorp/setup-terraform@v3")
for cmd in lint_cmds + test_cmds + build_cmds:
lines.append(f" - run: {cmd}")
return "\n".join(lines) + "\n"
@ -251,6 +266,22 @@ def gitlab_yaml(stack: Dict[str, Any]) -> str:
]
)
if not any(lang in langs for lang in ("node", "python", "go")):
# terraform/docker-only (or unrecognized) stacks: run detected commands directly
image = "hashicorp/terraform:1.9" if "terraform" in langs else "alpine:3.20"
for stage, cmds in (("lint", lint_cmds), ("test", test_cmds), ("build", build_cmds)):
lines.extend(
[
"",
f"generic_{stage}:",
f" image: {image}",
f" stage: {stage}",
" script:",
]
)
for cmd in cmds:
lines.append(f" - {cmd}")
return "\n".join(lines) + "\n"

View file

@ -82,6 +82,7 @@ def detect(repo: Path) -> StackReport:
"requirements": (repo / "requirements.txt").exists(),
"go_mod": (repo / "go.mod").exists(),
"dockerfile": (repo / "Dockerfile").exists(),
"terraform": any(repo.glob("*.tf")) or (repo / "terraform").is_dir(),
"vercel": (repo / "vercel.json").exists(),
"helm": (repo / "helm").exists() or (repo / "charts").exists(),
"k8s": (repo / "k8s").exists() or (repo / "kubernetes").exists(),
@ -107,6 +108,12 @@ def detect(repo: Path) -> StackReport:
if signals["go_mod"]:
languages.append("go")
if signals["terraform"]:
languages.append("terraform")
if signals["dockerfile"]:
languages.append("docker")
scripts = read_package_scripts(repo)
lint_commands: List[str] = []
test_commands: List[str] = []
@ -128,6 +135,16 @@ def detect(repo: Path) -> StackReport:
test_commands.append("go test ./...")
build_commands.append("go build ./...")
if "terraform" in languages:
tf_dir = "terraform" if (repo / "terraform").is_dir() and not any(repo.glob("*.tf")) else "."
lint_commands.append(f"terraform -chdir={tf_dir} fmt -check -recursive")
test_commands.append(f"terraform -chdir={tf_dir} validate")
build_commands.append(f"terraform -chdir={tf_dir} plan -input=false")
if "docker" in languages:
lint_commands.append("hadolint Dockerfile")
build_commands.append("docker build -t app:ci .")
return StackReport(
repo=str(repo.resolve()),
languages=sorted(set(languages)),

View file

@ -247,20 +247,33 @@ grep -n "new Array([0-9]\{4,\}\|Buffer\.alloc" /tmp/pr-$PR.diff | grep "^+"
gh pr view $PR --json body | jq -r '.body' | \
grep -oE "(PROJ-[0-9]+|[A-Z]+-[0-9]+|https://linear\.app/[^)\"]+)" | sort -u
# Verify Jira ticket exists (requires JIRA_API_TOKEN)
# Verify Jira ticket exists (requires JIRA_API_TOKEN to be SET in the environment).
# Credentials are fed to curl via a config read from stdin (-K -) so the token
# never appears in argv — `ps aux` / /proc/*/cmdline can't see it, and nothing
# secret lands in shell history. Never paste the raw token on the command line.
TICKET="PROJ-123"
curl -s -u "user@company.com:$JIRA_API_TOKEN" \
"https://your-org.atlassian.net/rest/api/3/issue/$TICKET" | \
: "${JIRA_API_TOKEN:?JIRA_API_TOKEN must be set}"
curl -s -K - "https://your-org.atlassian.net/rest/api/3/issue/$TICKET" <<EOF | \
jq '{key, summary: .fields.summary, status: .fields.status.name}'
user = "user@company.com:$JIRA_API_TOKEN"
EOF
# Linear ticket
# Linear ticket — same pattern: the Authorization header goes through the
# stdin config, not a -H flag, to keep the key out of the process list.
LINEAR_ID="abc-123"
curl -s -H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
: "${LINEAR_API_KEY:?LINEAR_API_KEY must be set}"
curl -s -K - -H "Content-Type: application/json" \
--data "{\"query\": \"{ issue(id: \\\"$LINEAR_ID\\\") { title state { name } } }\"}" \
https://api.linear.app/graphql | jq .
https://api.linear.app/graphql <<EOF | jq .
header = "Authorization: $LINEAR_API_KEY"
EOF
```
> **Security note:** for repeated Jira use, prefer a `~/.netrc` entry
> (`machine your-org.atlassian.net login user@company.com password <token>`,
> `chmod 600 ~/.netrc`) and call `curl -s --netrc …` — no secret material in
> the command at all.
---
## Complete Review Checklist (30+ Items)

View file

@ -39,22 +39,22 @@ npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill --agent
```bash
# Content Creator
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/content-creator
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/skills/content-creator
# Demand Generation & Acquisition
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/marketing-demand-acquisition
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/skills/marketing-demand-acquisition
# Product Marketing Strategy
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/marketing-strategy-pmm
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/skills/marketing-strategy-pmm
# App Store Optimization
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/app-store-optimization
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/skills/app-store-optimization
# Social Media Analyzer
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/social-media-analyzer
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/skills/social-media-analyzer
# Campaign Analytics
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/campaign-analytics
npx ai-agent-skills install alirezarezvani/claude-skills/marketing-skill/skills/campaign-analytics
```
**Supported Agents:** Claude Code, Cursor, VS Code, Copilot, Goose, Amp, Codex

View file

@ -38,22 +38,22 @@ npx ai-agent-skills install alirezarezvani/claude-skills/project-management --ag
```bash
# Senior Project Manager Expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/senior-pm
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/senior-pm
# Scrum Master Expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/scrum-master
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/scrum-master
# Atlassian Jira Expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/jira-expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/jira-expert
# Atlassian Confluence Expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/confluence-expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/confluence-expert
# Atlassian Administrator
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/atlassian-admin
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/atlassian-admin
# Atlassian Template Creator
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/atlassian-templates
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/atlassian-templates
```
**Supported Agents:** Claude Code, Cursor, VS Code, Copilot, Goose, Amp, Codex
@ -302,7 +302,7 @@ mcp__atlassian__searchJiraIssuesUsingJql (cloudId, jql="project = PROJ AND statu
1. **Install Senior PM Expert:**
```bash
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/senior-pm
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/senior-pm
```
2. **Use project charter template** from Atlassian Templates skill
@ -313,7 +313,7 @@ mcp__atlassian__searchJiraIssuesUsingJql (cloudId, jql="project = PROJ AND statu
1. **Install Scrum Master Expert:**
```bash
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/scrum-master
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/scrum-master
```
2. **Use sprint planning template** for next sprint
@ -324,7 +324,7 @@ mcp__atlassian__searchJiraIssuesUsingJql (cloudId, jql="project = PROJ AND statu
1. **Install Jira Expert:**
```bash
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/jira-expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/jira-expert
```
2. **Configure custom workflows** for your team
@ -335,7 +335,7 @@ mcp__atlassian__searchJiraIssuesUsingJql (cloudId, jql="project = PROJ AND statu
1. **Install Confluence Expert:**
```bash
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/confluence-expert
npx ai-agent-skills install alirezarezvani/claude-skills/project-management/skills/confluence-expert
```
2. **Design space architecture** for your organization

View file

@ -40,26 +40,26 @@ npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team --agent curs
```bash
# Strategic Leadership
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/regulatory-affairs-head
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/quality-manager-qmr
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/regulatory-affairs-head
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/quality-manager-qmr
# Quality Systems
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/quality-manager-qms-iso13485
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/capa-officer
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/quality-documentation-manager
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/quality-manager-qms-iso13485
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/capa-officer
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/quality-documentation-manager
# Risk & Security
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/risk-management-specialist
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/information-security-manager-iso27001
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/risk-management-specialist
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/information-security-manager-iso27001
# Regulatory Specialists
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/mdr-745-specialist
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/fda-consultant-specialist
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/mdr-745-specialist
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/fda-consultant-specialist
# Audit & Compliance
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/qms-audit-expert
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/isms-audit-expert
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/gdpr-dsgvo-expert
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/qms-audit-expert
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/isms-audit-expert
npx ai-agent-skills install alirezarezvani/claude-skills/ra-qm-team/skills/gdpr-dsgvo-expert
```
**Supported Agents:** Claude Code, Cursor, VS Code, Copilot, Goose, Amp, Codex

51
scripts/sync-codebuff-skills.py Executable file
View file

@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""
sync-codebuff-skills.py Install claude-code-skills into Codebuff.
Codebuff (https://codebuff.com) discovers agent skills from ~/.agents/skills/
using the agentskills.io standard (SKILL.md with YAML frontmatter) the same
format this repo uses, so no conversion is needed.
This is a thin wrapper around sync-vibe-skills.py: identical discovery and
flat-layout sync logic, different default target directory.
Usage:
python scripts/sync-codebuff-skills.py # full sync
python scripts/sync-codebuff-skills.py --verbose # show each skill
python scripts/sync-codebuff-skills.py --domain engineering # one domain
python scripts/sync-codebuff-skills.py --dry-run # preview only
python scripts/sync-codebuff-skills.py --copy # copy instead of symlink
Codebuff skill directory: ~/.agents/skills/
Skills land flat: ~/.agents/skills/<skill-name>/
"""
from __future__ import annotations
import importlib.util
import sys
from pathlib import Path
CODEBUFF_SKILLS_DIR = Path.home() / ".agents" / "skills"
def load_vibe_module():
"""Load sync-vibe-skills.py (hyphenated filename) as a module."""
path = Path(__file__).resolve().parent / "sync-vibe-skills.py"
spec = importlib.util.spec_from_file_location("sync_vibe_skills", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def main():
vibe = load_vibe_module()
# Reuse the vibe CLI wholesale with a codebuff default target.
if not any(arg.startswith("--target") for arg in sys.argv[1:]):
sys.argv.extend(["--target", str(CODEBUFF_SKILLS_DIR)])
vibe.VIBE_SKILLS_DIR = CODEBUFF_SKILLS_DIR
vibe.TOOL_NAME = "Codebuff"
vibe.main()
if __name__ == "__main__":
main()

View file

@ -11,14 +11,25 @@ Both tools use the agentskills.io standard (SKILL.md with YAML frontmatter),
so no format conversion is needed just symlink the directories.
Usage:
python scripts/sync-vibe-skills.py # full sync
python scripts/sync-vibe-skills.py # full sync (flat layout)
python scripts/sync-vibe-skills.py --verbose # show each skill
python scripts/sync-vibe-skills.py --domain engineering # one domain
python scripts/sync-vibe-skills.py --dry-run # preview only
python scripts/sync-vibe-skills.py --copy # copy instead of symlink
python scripts/sync-vibe-skills.py --nested # legacy namespaced layout
Vibe skill directory: ~/.vibe/skills/
Our skills land under: ~/.vibe/skills/claude-skills/<domain>/<skill-name>/
Layouts:
flat (default) ~/.vibe/skills/<skill-name>/
Vibe only discovers skills one directory below each configured
skill path, so this is the layout Vibe actually picks up out of
the box (issue #748). Name collisions across domains are resolved
as <domain>-<skill-name>.
nested (--nested) ~/.vibe/skills/claude-skills/<domain>/<skill-name>/
Legacy layout. Requires adding each domain directory to
`skill_paths` in ~/.vibe/config.toml, e.g.:
skill_paths = ["~/.vibe/skills/claude-skills/engineering"]
"""
from __future__ import annotations
@ -32,6 +43,7 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
VIBE_SKILLS_DIR = Path.home() / ".vibe" / "skills"
TARGET_SUBDIR = "claude-skills" # namespace to avoid collisions with Vibe built-in skills
TOOL_NAME = "Vibe" # overridden by wrapper scripts (e.g. sync-codebuff-skills.py)
# Domain directories that contain skills (each subdirectory with a SKILL.md)
DOMAIN_DIRS = [
@ -147,9 +159,33 @@ def read_frontmatter(skill_md):
return {}
def sync_skill(skill, target_root, use_copy, verbose, dry_run):
def assign_flat_names(skills):
"""Give each skill a unique directory name for the flat layout.
First skill keeps its bare name; collisions across domains become
<domain>-<skill-name> (and gain a numeric suffix in the unlikely case
that still collides).
"""
taken: set = set()
for s in skills:
candidate = s["name"]
if candidate in taken:
candidate = f"{s['domain']}-{s['name']}"
n = 2
while candidate in taken:
candidate = f"{s['domain']}-{s['name']}-{n}"
n += 1
taken.add(candidate)
s["flat_name"] = candidate
return skills
def sync_skill(skill, target_root, use_copy, verbose, dry_run, nested):
"""Create a symlink or copy for one skill."""
target = target_root / skill["domain"] / skill["name"]
if nested:
target = target_root / skill["domain"] / skill["name"]
else:
target = target_root / skill["flat_name"]
if target.exists() or target.is_symlink():
if verbose:
@ -180,10 +216,11 @@ def sync_skill(skill, target_root, use_copy, verbose, dry_run):
return "new"
def write_index(target_root, skills):
"""Write a skills-index.json for quick lookup."""
def write_index(target_root, skills, nested):
"""Write a skills index JSON for quick lookup."""
index = {
"source": "claude-code-skills",
"layout": "nested" if nested else "flat",
"total_skills": len(skills),
"domains": {},
}
@ -195,9 +232,12 @@ def write_index(target_root, skills):
index["domains"][d].append({
"name": s["name"],
"description": fm.get("description", ""),
"path": f"{d}/{s['name']}",
"path": f"{d}/{s['name']}" if nested else s["flat_name"],
})
index_path = target_root / "skills-index.json"
# In flat mode target_root is ~/.vibe/skills itself — use a namespaced
# filename so we never clobber anything Vibe owns.
filename = "skills-index.json" if nested else "claude-skills-index.json"
index_path = target_root / filename
index_path.write_text(json.dumps(index, indent=2), encoding="utf-8")
return index_path
@ -216,6 +256,12 @@ def main():
p.add_argument("--dry-run", action="store_true", help="Preview only, don't create files")
p.add_argument("--copy", action="store_true", help="Copy files instead of symlink")
p.add_argument("--json", action="store_true", help="JSON output")
p.add_argument(
"--nested",
action="store_true",
help="Legacy layout under claude-skills/<domain>/ — requires skill_paths "
"entries in ~/.vibe/config.toml; Vibe does NOT discover it by default",
)
p.add_argument(
"--target",
default=str(VIBE_SKILLS_DIR),
@ -223,9 +269,12 @@ def main():
)
args = p.parse_args()
target_root = Path(args.target).expanduser() / TARGET_SUBDIR
base = Path(args.target).expanduser()
target_root = base / TARGET_SUBDIR if args.nested else base
domains = [args.domain] if args.domain else None
skills = discover_skills(REPO_ROOT, domains)
if not args.nested:
assign_flat_names(skills)
if not skills:
msg = f"No skills found in {REPO_ROOT}"
@ -240,18 +289,19 @@ def main():
counts = {"new": 0, "skip": 0, "would": 0}
for s in skills:
result = sync_skill(s, target_root, args.copy, args.verbose, args.dry_run)
result = sync_skill(s, target_root, args.copy, args.verbose, args.dry_run, args.nested)
counts[result] += 1
# Write index
if not args.dry_run:
idx_path = write_index(target_root, skills)
idx_path = write_index(target_root, skills, args.nested)
else:
idx_path = target_root / "skills-index.json"
idx_path = target_root / ("skills-index.json" if args.nested else "claude-skills-index.json")
summary = {
"status": "ok",
"target": str(target_root),
"layout": "nested" if args.nested else "flat",
"total_skills": len(skills),
"new": counts["new"],
"skipped": counts["skip"],
@ -272,7 +322,12 @@ def main():
if not args.dry_run:
print(f" Index: {idx_path}")
print()
print("Vibe will discover these skills via /skills or /<skill-name>.")
if args.nested:
print(f"NOTE: the nested layout is NOT discovered by {TOOL_NAME} out of the box.")
print("Add each domain to skill_paths in ~/.vibe/config.toml, e.g.:")
print(' skill_paths = ["~/.vibe/skills/claude-skills/engineering"]')
else:
print(f"{TOOL_NAME} will discover these skills via /skills or /<skill-name>.")
print("No format conversion needed — both tools use agentskills.io SKILL.md standard.")