@polymorphism-tech/morph-spec 7.5.0 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +18 -0
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +12 -4
  7. package/framework/MORPH.md +18 -10
  8. package/framework/agents.json +13 -5
  9. package/framework/commands/morph-preflight.md +10 -5
  10. package/framework/hooks/README.md +8 -10
  11. package/framework/hooks/claude-code/core/gate-guard.js +36 -16
  12. package/framework/hooks/claude-code/core/protect-spec-files.js +20 -5
  13. package/framework/hooks/claude-code/core/tasks-json-guard.js +81 -0
  14. package/framework/hooks/shared/tasks-validator.js +68 -0
  15. package/framework/mcp/registry.json +5 -48
  16. package/framework/rubrics/backend.json +3 -3
  17. package/framework/rubrics/default.json +3 -3
  18. package/framework/rubrics/frontend.json +3 -3
  19. package/framework/rules/infrastructure-standards.md +4 -4
  20. package/framework/skills/core/create-umbraco-lp/SKILL.md +22 -2
  21. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +84 -5
  22. package/framework/skills/core/morph-apply/SKILL.md +53 -40
  23. package/framework/skills/core/morph-brainstorming/SKILL.md +7 -12
  24. package/framework/skills/core/morph-eval/SKILL.md +10 -8
  25. package/framework/skills/core/morph-implement/SKILL.md +27 -21
  26. package/framework/skills/core/morph-plan/SKILL.md +12 -21
  27. package/framework/skills/core/morph-proposal/SKILL.md +3 -8
  28. package/framework/skills/core/morph-review/SKILL.md +30 -14
  29. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  30. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  31. package/framework/standards/STANDARDS.json +80 -8
  32. package/framework/standards/ai-agents/durable-workflows-hitl.md +191 -0
  33. package/framework/standards/ai-agents/rag-custom-pgvector.md +246 -243
  34. package/framework/standards/ai-agents/setup.md +3 -0
  35. package/framework/standards/ai-agents/vector-data-extensions.md +170 -0
  36. package/framework/standards/ai-agents/workflows.md +190 -187
  37. package/framework/standards/infrastructure/docker/{easypanel-deploy.md → coolify-deploy.md} +49 -20
  38. package/framework/standards/integration/mcp/mcp-tools.md +38 -43
  39. package/framework/templates/README.md +7 -7
  40. package/framework/templates/REGISTRY.json +14 -14
  41. package/framework/templates/context/CONTEXT.md +3 -3
  42. package/framework/templates/docs/proposal.md +2 -2
  43. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +1 -1
  44. package/framework/templates/infrastructure/docker/coolify.template.json +49 -0
  45. package/framework/templates/infrastructure/github/README.md +6 -6
  46. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +33 -0
  47. package/package.json +1 -1
  48. package/src/commands/project/doctor.js +36 -1
  49. package/src/commands/state/approve.js +29 -4
  50. package/src/commands/state/score.js +88 -0
  51. package/src/commands/tasks/dag.js +68 -0
  52. package/src/dashboard/scanner/rubrics-scanner.js +1 -1
  53. package/src/lib/generators/recap-generator.js +1 -1
  54. package/src/lib/tasks/dispatch-planner.js +201 -0
  55. package/src/lib/validators/score-aggregator.js +3 -3
  56. package/src/scripts/setup-infra.js +42 -0
  57. package/src/utils/agents-installer.js +142 -10
  58. package/src/utils/hooks-installer.js +13 -5
  59. package/framework/rubrics/eval-rubric.json +0 -66
  60. package/framework/templates/infrastructure/docker/easypanel.template.json +0 -46
  61. package/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
@@ -1,23 +1,39 @@
1
- # EasyPanel Deployment Standard
1
+ # Coolify Deployment Standard
2
2
 
3
3
  > **Scope:** nextjs-neon
4
4
  > **Layer:** 2 (on keyword)
5
- > **Keywords:** easypanel, docker, deploy, deployment, container
6
- > **Load When:** easypanel or docker deployment keywords detected
5
+ > **Keywords:** coolify, docker, deploy, deployment, container, vps, ssh
6
+ > **Load When:** coolify or docker deployment keywords detected
7
7
 
8
- **Verified against:** .NET 10 + Next.js 15 + Node 22 (Docker base images). Last-verified: 2026-05-20.
8
+ **Verified against:** .NET 10 + Next.js 15 + Node 22 (Docker base images). Last-verified: 2026-07-07.
9
9
 
10
10
  ---
11
11
 
12
12
  Stack: Next.js 15 + Neon PostgreSQL + .NET Backend
13
13
 
14
+ ## What is Coolify and why we use it
15
+
16
+ [Coolify](https://coolify.io) is a self-hosted, open-source PaaS -- an alternative to Heroku/Vercel/Railway that runs on your own VPS. It manages Docker-based deployments end-to-end: builds from a Dockerfile or docker-compose, reverse-proxies traffic (Traefik under the hood), issues and renews Let's Encrypt SSL certificates, and exposes a REST API for automation.
17
+
18
+ Coolify is Polymorphism Tech's standard self-hosted deployment platform for Docker workloads.
19
+
20
+ There is no MCP server or CLI tool for Coolify. All access is via **SSH into the VPS**, from which the Coolify API is reached at `http://localhost:8000/api/v1/` (it is not exposed publicly). The API token lives server-side only (e.g. `~/.coolify-token`, `chmod 600`) -- never print it, never commit it, never let it leave the SSH session.
21
+
22
+ ```bash
23
+ # Canonical access pattern — token is read and used entirely server-side
24
+ ssh <vps-ssh-alias> 'T=$(cat ~/.coolify-token); curl -s -H "Authorization: Bearer $T" http://localhost:8000/api/v1/projects'
25
+ ```
26
+
27
+ Useful endpoints: `/version`, `/projects`, `/servers`, `/applications`, `/deploy?uuid={uuid}`. Changing a domain: `PATCH /applications/{uuid}` with `{"domains":"https://x,https://www.x"}`, then `GET /deploy?uuid={uuid}` to redeploy.
28
+
14
29
  ## Core Rules
15
30
 
16
31
  - ALWAYS use multi-stage Docker builds for minimal image size
17
32
  - ALWAYS configure health checks for zero-downtime deploys
18
- - NEVER hardcode secrets in Dockerfiles -- use EasyPanel environment variables
33
+ - NEVER hardcode secrets in Dockerfiles -- use Coolify environment variables
19
34
  - ALWAYS use `.dockerignore` to exclude node_modules, .git, .env files
20
35
  - SSL is automatic via Let's Encrypt -- no manual certificate management
36
+ - NEVER call the Coolify API directly from your local machine or from CI without going through SSH -- the API port is not publicly reachable and the token must stay server-side
21
37
 
22
38
  ## .NET 10 Dockerfile
23
39
 
@@ -85,7 +101,7 @@ test
85
101
  __tests__
86
102
  ```
87
103
 
88
- ## EasyPanel Service Config
104
+ ## Coolify Service Config
89
105
 
90
106
  | Setting | .NET Backend | Next.js Frontend |
91
107
  |---------|-------------|-----------------|
@@ -97,20 +113,22 @@ __tests__
97
113
 
98
114
  ### GitHub Integration
99
115
 
100
- 1. Generate GitHub PAT with `repo` scope
101
- 2. EasyPanel: Settings > GitHub > Add token
102
- 3. Select repository and branch
103
- 4. Enable "Auto Deploy" for webhook-triggered deploys
116
+ Coolify deploys straight from a GitHub repo (via GitHub App install or a deploy key) and redeploys automatically on push when auto-deploy is enabled for the application.
117
+
118
+ 1. In Coolify, add the GitHub source (GitHub App install or deploy key)
119
+ 2. Create the Application, pointing it at the repo/branch and the Dockerfile path
120
+ 3. Enable "Auto Deploy" for webhook-triggered deploys on push to the tracked branch
121
+ 4. Record the application's `uuid` -- every subsequent API call (`/deploy?uuid=`) targets it
104
122
 
105
123
  ### Domain and SSL
106
124
 
107
- DNS setup (CNAME to EasyPanel server):
125
+ DNS setup (A record to the Coolify server):
108
126
  ```
109
- app.example.com CNAME your-server.easypanel.host
110
- api.example.com CNAME your-server.easypanel.host
127
+ app.example.com A <vps-ip>
128
+ api.example.com A <vps-ip>
111
129
  ```
112
130
 
113
- SSL via Let's Encrypt is automatic. Force HTTPS enabled by default.
131
+ SSL via Let's Encrypt is automatic (Coolify runs Traefik as the reverse proxy). Force HTTPS enabled by default.
114
132
 
115
133
  ## Environment Variables
116
134
 
@@ -147,11 +165,11 @@ export async function GET() {
147
165
 
148
166
  ## Zero-Downtime Deploys
149
167
 
150
- EasyPanel uses Docker HEALTHCHECK to determine container readiness:
168
+ Coolify uses the same Docker HEALTHCHECK contract to determine container readiness:
151
169
 
152
170
  1. New container starts alongside old container
153
171
  2. Health check passes after start-period + retries
154
- 3. Traffic shifts to new container
172
+ 3. Traffic shifts to new container (Traefik)
155
173
  4. Old container stopped
156
174
 
157
175
  | HEALTHCHECK Param | Value | Purpose |
@@ -163,8 +181,8 @@ EasyPanel uses Docker HEALTHCHECK to determine container readiness:
163
181
 
164
182
  ## Monitoring
165
183
 
166
- - **Logs**: Real-time in EasyPanel UI
167
- - **Metrics**: CPU, memory, network via dashboard
184
+ - **Logs**: `docker logs <container>` over SSH, or Coolify's live log viewer in the UI
185
+ - **Metrics**: CPU, memory, network via Coolify's dashboard, or `docker stats` over SSH
168
186
  - **Restart**: Auto-restart on crash (default)
169
187
 
170
188
  Structured logging:
@@ -182,6 +200,17 @@ import pino from "pino";
182
200
  const logger = pino({ level: process.env.LOG_LEVEL ?? "info" });
183
201
  ```
184
202
 
203
+ ## Triggering a Deploy via SSH + API
204
+
205
+ Since there is no MCP server or CLI for Coolify, every deploy operation (manual redeploy, CI/CD, or troubleshooting) goes through the same SSH + curl pattern used for all other Coolify access:
206
+
207
+ ```bash
208
+ # Redeploy an existing application after a new image/commit is ready
209
+ ssh <vps-ssh-alias> 'T=$(cat ~/.coolify-token); curl -s -X GET -H "Authorization: Bearer $T" "http://localhost:8000/api/v1/deploy?uuid=<application-uuid>"'
210
+ ```
211
+
212
+ In GitHub Actions this becomes an SSH step using a deploy key stored as a secret (see the `github-workflow-deploy-coolify` template) -- never a direct HTTP call to the Coolify API from the runner, since the API port is not reachable outside the VPS.
213
+
185
214
  ## Deployment Checklist
186
215
 
187
216
  | Step | Action |
@@ -189,8 +218,8 @@ const logger = pino({ level: process.env.LOG_LEVEL ?? "info" });
189
218
  | 1 | Verify `output: "standalone"` in next.config.ts |
190
219
  | 2 | Test Docker build locally |
191
220
  | 3 | Test health endpoint |
192
- | 4 | Configure env vars in EasyPanel |
221
+ | 4 | Configure env vars in Coolify (UI or API) |
193
222
  | 5 | Set up custom domain + DNS |
194
223
  | 6 | Verify SSL certificate |
195
224
  | 7 | Enable auto-deploy from GitHub |
196
- | 8 | Push to main, verify deployment |
225
+ | 8 | Push to main, verify deployment via SSH (`docker ps` / Coolify logs) |
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** mcp, model-context-protocol, mcp-tools, context7, playwright, mcp-client
6
6
  > **Load When:** an MCP tool is used in any morph-spec phase
7
7
 
8
- **Verified against:** Claude Code MCP tooling — context7, Playwright, GitHub, Neon CLI. Last-verified: 2026-05-20.
8
+ **Verified against:** Claude Code MCP tooling — context7, Playwright; GitHub via `gh` CLI; Neon via `neonctl`. Last-verified: 2026-07-08.
9
9
 
10
10
  ---
11
11
 
@@ -19,8 +19,8 @@ Before using any MCP tool, check if it's available in the current session:
19
19
  // Pattern: Attempt the call — if the tool doesn't exist, Claude Code will report it
20
20
  // There is no "list MCPs" command; just try the most common operation
21
21
 
22
- // GitHub MCP
23
- await mcp__github__get_repo();
22
+ // GitHub — o morph-spec NÃO usa MCP do GitHub: use o `gh` CLI via Bash
23
+ // gh repo view --json name,defaultBranchRef,languages
24
24
 
25
25
  // Context7 (library docs)
26
26
  await mcp__context7__resolve_library_id({ libraryName: "react" });
@@ -41,18 +41,19 @@ await mcp__playwright__browser_navigate({ url: "https://example.com" });
41
41
 
42
42
  ### Phase 1 (Setup) — Project Discovery
43
43
 
44
- | MCP | Use Case | Example |
45
- |-----|----------|---------|
46
- | **GitHub** | Repo info, recent PRs, issues | `mcp__github__get_repo()` |
44
+ | Tool | Use Case | Example |
45
+ |------|----------|---------|
46
+ | **GitHub CLI (`gh`)** | Repo info, recent PRs, issues | `gh repo view --json ...` (Bash) |
47
47
  | **Filesystem** | Scan project structure | Native Glob/Read preferred |
48
48
 
49
- ```javascript
50
- // Detect project type from repo metadata
51
- const repo = await mcp__github__get_repo();
52
- // → language, default_branch, topics
49
+ ```bash
50
+ # Detect project type from repo metadata (gh CLI via Bash — sem MCP)
51
+ gh repo view --json name,defaultBranchRef,languages,repositoryTopics
52
+ gh pr list --limit 10 --json title,state,updatedAt
53
+ gh issue list --limit 10 --json title,labels
53
54
 
54
- // Fallback: use Glob to detect stack
55
- // Glob: "**/{package.json,*.csproj,*.sln,go.mod,Cargo.toml}"
55
+ # Fallback: use Glob to detect stack
56
+ # Glob: "**/{package.json,*.csproj,*.sln,go.mod,Cargo.toml}"
56
57
  ```
57
58
 
58
59
  ### Phase 2 (UI/UX) — Design References
@@ -143,7 +144,7 @@ await mcp__gemini_image_mcp__continue_editing({
143
144
  |------|----------|---------|
144
145
  | **Neon CLI (`neonctl`) / psql** | Database schema, tables, relationships, RLS | `psql <DB_URL> -c "SELECT ..."` |
145
146
  | **Context7** | Library docs for architecture decisions | `mcp__context7__query_docs()` |
146
- | **GitHub** | Check existing code patterns, PRs | `mcp__github__search_code()` |
147
+ | **GitHub CLI (`gh`)** | Check existing code patterns, PRs | `gh search code` (Bash) |
147
148
 
148
149
  ```bash
149
150
  # === SCHEMA ANALYSIS (Critical for contracts.cs) ===
@@ -183,7 +184,7 @@ psql $(neonctl connection-string) -c "SELECT tablename, policyname, cmd, qual FR
183
184
  | MCP | Use Case | Example |
184
185
  |-----|----------|---------|
185
186
  | **Context7** | Verify library capabilities, API limits | `mcp__context7__query_docs()` |
186
- | **GitHub** | Check issue discussions, known limitations | `mcp__github__search_issues()` |
187
+ | **GitHub CLI (`gh`)** | Check issue discussions, known limitations | `gh search issues` (Bash) |
187
188
 
188
189
  ```javascript
189
190
  // Verify if library supports a required feature
@@ -198,24 +199,17 @@ const docs = await mcp__context7__query_docs({
198
199
 
199
200
  ### Phase 6 (Tasks) — Planning & Organization
200
201
 
201
- | MCP | Use Case | Example |
202
- |-----|----------|---------|
203
- | **GitHub** | Create issues from tasks, link to milestone | `mcp__github__create_issue()` |
202
+ | Tool | Use Case | Example |
203
+ |------|----------|---------|
204
+ | **GitHub CLI (`gh`)** | Create issues from tasks, link to milestone | `gh issue create` (Bash) |
204
205
  | **Context7** | Estimate complexity based on library docs | `mcp__context7__query_docs()` |
205
206
 
206
- ```javascript
207
- // Create GitHub issues from tasks.json (if team uses GitHub Projects)
208
- for (const task of tasks) {
209
- await mcp__github__create_issue({
210
- title: task.title,
211
- body: task.description,
212
- labels: [task.category],
213
- milestone: featureMilestone
214
- });
215
- }
216
-
217
- // Fallback: Bash with gh CLI
218
- // gh issue create --title "T001: Create Entity Lead" --label "domain"
207
+ ```bash
208
+ # Create GitHub issues from tasks.json (if team uses GitHub Projects)
209
+ # — one `gh issue create` per task, iterating tasks.json:
210
+ gh issue create --title "T001: Create Entity Lead" \
211
+ --body "description + doneCriteria da task" \
212
+ --label "backend" --milestone "feature-x"
219
213
  ```
220
214
 
221
215
  ### Phase 7 (Implement) — Build & Deploy
@@ -223,7 +217,7 @@ for (const task of tasks) {
223
217
  | Tool | Use Case | Example |
224
218
  |------|----------|---------|
225
219
  | **Neon CLI (`neonctl`) / psql** | Run migrations, create RLS policies | `dotnet ef database update` |
226
- | **GitHub** | Create PR, push branches | `mcp__github__create_pull_request()` |
220
+ | **GitHub CLI (`gh`)** | Create PR, push branches | `gh pr create` (Bash) |
227
221
  | **Context7** | Look up API usage during coding | `mcp__context7__query_docs()` |
228
222
  | **Playwright** | Smoke test deployed features, verify UI | `mcp__playwright__browser_navigate()` |
229
223
  | **Vercel** | Deploy, check logs, manage env vars | `mcp__vercel__list_projects()` |
@@ -283,7 +277,7 @@ await mcp__vercel__manage_env_vars({
283
277
  target: ['production', 'preview']
284
278
  });
285
279
 
286
- // Fallback: Bash for gh CLI for PRs, vercel CLI for deployments
280
+ // PRs/issues: sempre `gh` CLI (Bash). Deployments fallback: vercel CLI
287
281
  ```
288
282
 
289
283
  ---
@@ -293,7 +287,7 @@ await mcp__vercel__manage_env_vars({
293
287
  | Need | MCP Tool | Native Alternative |
294
288
  |------|----------|--------------------|
295
289
  | Database schema | Neon CLI (`neonctl`) / psql | Grep queries + Read types |
296
- | Repo metadata | GitHub MCP | Bash `gh` CLI |
290
+ | Repo metadata / issues / PRs | Bash `gh` CLI | WebFetch `api.github.com` (read-only) |
297
291
  | Library docs | Context7 | WebSearch + WebFetch |
298
292
  | Live page preview | Playwright MCP | WebFetch URL |
299
293
  | Page interaction (click, type, navigate) | Playwright MCP | Manual testing |
@@ -304,7 +298,7 @@ await mcp__vercel__manage_env_vars({
304
298
  | Vercel deployments | Vercel MCP | Bash `vercel` CLI |
305
299
  | AI image generation | Gemini Image MCP | Manual creation or stock photos |
306
300
 
307
- **Rule:** MCP tools provide structured data (JSON responses). Native tools require manual parsing. **Always prefer MCP when available** — fall back to native when not. **Exceptions:** Neon, Docker, and Azure always use their respective CLIs directly — never MCP.
301
+ **Rule:** MCP tools provide structured data (JSON responses). Native tools require manual parsing. **Always prefer MCP when available** — fall back to native when not. **Exceptions:** Neon, Docker, Azure **and GitHub** always use their respective CLIs directly — never MCP (`gh` já retorna JSON estruturado via `--json`, sem Docker nem PAT em env var).
308
302
 
309
303
  ---
310
304
 
@@ -445,16 +439,17 @@ const docs = await mcp__context7__query_docs({
445
439
  });
446
440
  ```
447
441
 
448
- ### GitHub: Code Search Across Repo
442
+ ### GitHub: Code Search Across Repo (`gh` CLI — sem MCP)
449
443
 
450
- > **v4 config:** Docker — `{ "command": "docker", "args": ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"] }`. Requires Docker Desktop.
444
+ O morph-spec **não instala nem usa MCP do GitHub**. O `gh` CLI (autenticado uma vez via `gh auth login`) cobre a mesma superfície com JSON estruturado, sem Docker e sem PAT em variável de ambiente:
451
445
 
452
- ```javascript
453
- // Find patterns in large codebase during Phase 2
454
- const results = await mcp__github__search_code({
455
- query: "DbSet leads repo:myorg/myrepo",
456
- });
457
- // → Find all places that query the leads table
446
+ ```bash
447
+ # Find patterns in large codebase during Phase 2
448
+ gh search code "DbSet leads" --repo myorg/myrepo --json path,textMatches
449
+ # → Find all places that query the leads table
450
+
451
+ # Qualquer endpoint REST sem subcomando dedicado:
452
+ gh api repos/{owner}/{repo}/branches --jq '.[].name'
458
453
  ```
459
454
 
460
455
  ---
@@ -463,7 +458,7 @@ const results = await mcp__github__search_code({
463
458
 
464
459
  - **Never pass secrets** in MCP tool parameters (API keys, tokens, passwords)
465
460
  - **Neon** uses neonctl/psql — use database branches for development, not production
466
- - **GitHub MCP** inherits the configured token's permissions
461
+ - **GitHub via `gh` CLI** herda as permissões do login do `gh auth login` (keyring) — sem PAT em env var, sem Docker
467
462
  - **Always validate tool responses** before using in contracts or code generation
468
463
 
469
464
  ---
@@ -54,18 +54,18 @@ framework/templates/
54
54
  │ └── postgresql-rls-policy.sql # Row-level security
55
55
  │
56
56
  ├── infrastructure/
57
- │ ├── docker/ # Docker & EasyPanel
57
+ │ ├── docker/ # Docker & Coolify
58
58
  │ │ ├── dockerfile-api.dockerfile # .NET API
59
59
  │ │ ├── dockerfile-web.dockerfile # Next.js web
60
60
  │ │ ├── Dockerfile.template # Generic
61
61
  │ │ ├── docker-compose.template.yml # Compose
62
- │ │ └── easypanel.template.json # EasyPanel config
62
+ │ │ └── coolify.template.json # Coolify config
63
63
  │ │
64
64
  │ └── github/ # GitHub Actions CI/CD
65
65
  │ ├── workflows/ # Reusable workflow templates
66
66
  │ │ ├── dotnet-build.yml.hbs # .NET restore, build, test
67
67
  │ │ ├── docker-build-push.yml.hbs # Docker build & push
68
- │ │ └── deploy-easypanel.yml.hbs # EasyPanel deployment
68
+ │ │ └── deploy-coolify.yml.hbs # Coolify deployment (via SSH)
69
69
  │ │
70
70
  │ ├── actions/ # Composite action templates
71
71
  │ │ ├── docker-build-push/action.yml.hbs
@@ -313,7 +313,7 @@ infrastructure/github/
313
313
  ├── workflows/ # Reusable workflow templates
314
314
  │ ├── dotnet-build.yml.hbs # .NET restore, build, test
315
315
  │ ├── docker-build-push.yml.hbs # Docker build & push
316
- │ └── deploy-easypanel.yml.hbs # EasyPanel deployment
316
+ │ └── deploy-coolify.yml.hbs # Coolify deployment via SSH
317
317
  │
318
318
  └── actions/ # Composite action templates
319
319
  ├── docker-build-push/action.yml.hbs
@@ -329,8 +329,8 @@ infrastructure/github/
329
329
 
330
330
  ### Deployment Strategy Difference
331
331
 
332
- **NextJS-Neon:** Docker containers to EasyPanel
333
- - `docker build` → `docker push` → EasyPanel webhook
332
+ **NextJS-Neon:** Docker containers to Coolify
333
+ - `docker build` → `docker push` → Coolify deploy triggered via SSH (Coolify's API is not publicly exposed — the runner SSHes into the VPS and calls `localhost:8000/api/v1/deploy` there)
334
334
  - Multi-service deployment (API + Web)
335
335
  - Registry: GitHub Container Registry (ghcr.io)
336
336
 
@@ -368,7 +368,7 @@ infrastructure/github/
368
368
  ```
369
369
 
370
370
  **GitHub Secrets to Configure:**
371
- - NextJS-Neon: `GITHUB_TOKEN` (auto), `EASYPANEL_API_TOKEN`
371
+ - NextJS-Neon: `GITHUB_TOKEN` (auto), `COOLIFY_SSH_HOST`, `COOLIFY_SSH_USER`, `COOLIFY_SSH_KEY`, `COOLIFY_APPLICATION_UUID`
372
372
 
373
373
  ### Stack-Specific Overrides
374
374
 
@@ -127,6 +127,8 @@
127
127
  "APP_INSIGHTS",
128
128
  "AZURE_LOCATION",
129
129
  "CONTAINER_ENV",
130
+ "COOLIFY_APP_URL",
131
+ "COOLIFY_SERVER",
130
132
  "DEVOPS_AREA_PATH",
131
133
  "DEVOPS_BUILD_PIPELINE",
132
134
  "DEVOPS_ORG",
@@ -134,8 +136,6 @@
134
136
  "DEVOPS_RELEASE_PIPELINE",
135
137
  "DEVOPS_WIKI_NAME",
136
138
  "DOTNET_VERSION",
137
- "EASYPANEL_API_URL",
138
- "EASYPANEL_WEB_URL",
139
139
  "EF_VERSION",
140
140
  "INFERRED_STANDARDS",
141
141
  "MORPH_VERSION",
@@ -519,7 +519,7 @@
519
519
  "category": "infrastructure",
520
520
  "phase": "implement",
521
521
  "required": false,
522
- "description": ".NET API Dockerfile for EasyPanel/Docker",
522
+ "description": ".NET API Dockerfile for Coolify/Docker",
523
523
  "outputName": "Dockerfile.api",
524
524
  "placeholders": [],
525
525
  "stackSpecific": false,
@@ -540,7 +540,7 @@
540
540
  "category": "infrastructure",
541
541
  "phase": "implement",
542
542
  "required": false,
543
- "description": "Next.js web Dockerfile for EasyPanel/Docker",
543
+ "description": "Next.js web Dockerfile for Coolify/Docker",
544
544
  "outputName": "Dockerfile.web",
545
545
  "placeholders": [],
546
546
  "stackSpecific": false,
@@ -595,16 +595,16 @@
595
595
  "validatePlaceholders": true
596
596
  },
597
597
  {
598
- "id": "easypanel-config",
598
+ "id": "coolify-config",
599
599
  "kind": "template",
600
- "name": "EasyPanel Configuration",
601
- "path": "infrastructure/docker/easypanel.template.json",
600
+ "name": "Coolify Configuration",
601
+ "path": "infrastructure/docker/coolify.template.json",
602
602
  "location": "framework",
603
603
  "category": "infrastructure",
604
604
  "phase": "implement",
605
605
  "required": false,
606
- "description": "EasyPanel deployment configuration",
607
- "outputName": "easypanel.json",
606
+ "description": "Coolify deployment configuration — accessed via SSH + API, no MCP/CLI",
607
+ "outputName": "coolify.json",
608
608
  "placeholders": [
609
609
  "GITHUB_OWNER",
610
610
  "GITHUB_REPO"
@@ -835,17 +835,17 @@
835
835
  "validatePlaceholders": true
836
836
  },
837
837
  {
838
- "id": "github-workflow-deploy-easypanel",
838
+ "id": "github-workflow-deploy-coolify",
839
839
  "kind": "template",
840
- "name": "GitHub Actions - Deploy to EasyPanel",
841
- "path": "infrastructure/github/workflows/deploy-easypanel.yml.hbs",
840
+ "name": "GitHub Actions - Deploy to Coolify",
841
+ "path": "infrastructure/github/workflows/deploy-coolify.yml.hbs",
842
842
  "location": "framework",
843
843
  "category": "infrastructure",
844
844
  "technology": "github-actions",
845
845
  "phase": "deploy",
846
846
  "required": false,
847
- "description": "Reusable workflow for deploying to EasyPanel via webhook",
848
- "outputName": "deploy-easypanel.yml",
847
+ "description": "Reusable workflow for deploying to Coolify via SSH (API is not publicly exposed)",
848
+ "outputName": "deploy-coolify.yml",
849
849
  "placeholders": [],
850
850
  "stackSpecific": false,
851
851
  "applicableStacks": [
@@ -40,9 +40,9 @@
40
40
  {{#if MONITORING_ENABLED}}- **Application Insights** ({{APP_INSIGHTS}}){{/if}}
41
41
  {{/if}}
42
42
 
43
- {{#if EASYPANEL_ENABLED}}- **EasyPanel** (Docker Deployment)
44
- - API: {{EASYPANEL_API_URL}}
45
- - Web: {{EASYPANEL_WEB_URL}}
43
+ {{#if COOLIFY_ENABLED}}- **Coolify** (Docker Deployment via SSH)
44
+ - Server: {{COOLIFY_SERVER}}
45
+ - App: {{COOLIFY_APP_URL}}
46
46
  {{/if}}
47
47
 
48
48
  ---
@@ -70,7 +70,7 @@ Verifiable conditions that mean this feature is done:
70
70
  | **Database** | Neon (PostgreSQL + RLS) | {tables, policies, storage} |
71
71
  | **Backend API** | .NET 10 Minimal API | {endpoints, business logic} |
72
72
  | **Frontend** | Next.js 15 (App Router) | {pages, components, auth} |
73
- | **Infra** | EasyPanel + Docker | {deployment, SSL} |
73
+ | **Infra** | Coolify + Docker | {deployment, SSL} |
74
74
  {{/if}}
75
75
 
76
76
  ### User Journey
@@ -90,7 +90,7 @@ Verifiable conditions that mean this feature is done:
90
90
  | Resource | Provider | Monthly Cost | Notes |
91
91
  |----------|----------|-------------|-------|
92
92
  | Neon | Free / Launch | $0 - $19 | {DB, Connection Pooling, Branching} |
93
- | VPS (EasyPanel) | {provider} | ${X} | {API + Web containers} |
93
+ | VPS (Coolify) | {provider} | ${X} | {API + Web containers} |
94
94
  | External APIs | {provider} | ${X} | {if applicable} |
95
95
  | **Total** | | **${X}/month** | |
96
96
 
@@ -1,4 +1,4 @@
1
- # Multi-stage Next.js production build for EasyPanel
1
+ # Multi-stage Next.js production build for Coolify
2
2
  # Stage 1: Dependencies
3
3
  FROM node:20-alpine AS deps
4
4
  RUN apk add --no-cache libc6-compat
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "{{kebabCase FEATURE_NAME}}",
3
+ "provider": "coolify",
4
+ "access": {
5
+ "method": "ssh",
6
+ "note": "No MCP/CLI exists for Coolify — replace sshHost with the real SSH alias for the target VPS. The API is reached only from inside the VPS, at apiBaseUrl.",
7
+ "sshHost": "REPLACE_WITH_VPS_SSH_ALIAS",
8
+ "apiBaseUrl": "http://localhost:8000/api/v1",
9
+ "tokenPath": "~/.coolify-token"
10
+ },
11
+ "application": {
12
+ "uuid": "REPLACE_WITH_COOLIFY_APPLICATION_UUID",
13
+ "projectUuid": "REPLACE_WITH_COOLIFY_PROJECT_UUID",
14
+ "serverUuid": "REPLACE_WITH_COOLIFY_SERVER_UUID"
15
+ },
16
+ "source": {
17
+ "type": "github",
18
+ "owner": "{{GITHUB_OWNER}}",
19
+ "repo": "{{GITHUB_REPO}}",
20
+ "branch": "main",
21
+ "autoDeploy": true
22
+ },
23
+ "build": {
24
+ "type": "dockerfile",
25
+ "dockerfile": "Dockerfile"
26
+ },
27
+ "deploy": {
28
+ "replicas": 1,
29
+ "zeroDowntime": true
30
+ },
31
+ "env": [
32
+ {
33
+ "key": "NODE_ENV",
34
+ "value": "production"
35
+ },
36
+ {
37
+ "key": "DATABASE_URL",
38
+ "value": "${DATABASE_URL}"
39
+ }
40
+ ],
41
+ "domains": [
42
+ {
43
+ "host": "{{kebabCase FEATURE_NAME}}.example.com",
44
+ "port": 3000,
45
+ "https": true
46
+ }
47
+ ],
48
+ "deployCommand": "ssh REPLACE_WITH_VPS_SSH_ALIAS 'T=$(cat ~/.coolify-token); curl -s -X GET -H \"Authorization: Bearer $T\" \"http://localhost:8000/api/v1/deploy?uuid=REPLACE_WITH_COOLIFY_APPLICATION_UUID\"'"
49
+ }
@@ -1,16 +1,16 @@
1
1
  # GitHub Actions Templates for MORPH-SPEC
2
2
 
3
- > **Framework-Level CI/CD Templates for EasyPanel/Docker Deployments**
3
+ > **Framework-Level CI/CD Templates for Coolify/Docker Deployments**
4
4
 
5
5
  ## Overview
6
6
 
7
- Reusable GitHub Actions workflows and composite actions for the **nextjs-neon** stack (EasyPanel/Docker deployment).
7
+ Reusable GitHub Actions workflows and composite actions for the **nextjs-neon** stack (Coolify/Docker deployment).
8
8
 
9
9
  **Key Features:**
10
10
  - Handlebars v2.0 templating with helpers
11
11
  - Composite actions for shared logic
12
12
  - Docker multi-stage builds (.NET API + Next.js Web)
13
- - EasyPanel webhook deployment
13
+ - Coolify deployment via SSH (no MCP/CLI exists — the Coolify API is not publicly exposed, so the runner SSHes into the VPS to trigger it)
14
14
 
15
15
  ---
16
16
 
@@ -21,7 +21,7 @@ infrastructure/github/
21
21
  ├── workflows/
22
22
  │ ├── dotnet-build.yml.hbs # .NET restore, build, test, coverage
23
23
  │ ├── docker-build-push.yml.hbs # Docker build & push to registry
24
- │ └── deploy-easypanel.yml.hbs # EasyPanel deployment via webhook
24
+ │ └── deploy-coolify.yml.hbs # Coolify deployment via SSH
25
25
  │
26
26
  └── actions/
27
27
  ├── docker-build-push/action.yml.hbs # Build & push Docker image
@@ -38,8 +38,8 @@ infrastructure/github/
38
38
  - Multi-stage Docker build
39
39
  - Push to container registry (GHCR or Docker Hub)
40
40
 
41
- ### Deploy EasyPanel (`deploy-easypanel.yml.hbs`)
42
- - Triggers EasyPanel webhook after Docker push
41
+ ### Deploy Coolify (`deploy-coolify.yml.hbs`)
42
+ - SSHes into the VPS and calls the local Coolify API (`localhost:8000/api/v1/deploy?uuid=...`) after Docker push
43
43
  - Runs health check after deployment
44
44
  - Supports staging + production environments
45
45
 
@@ -0,0 +1,33 @@
1
+ name: Deploy to Coolify
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ environment:
7
+ required: true
8
+ type: string
9
+ image:
10
+ required: true
11
+ type: string
12
+
13
+ jobs:
14
+ deploy:
15
+ runs-on: ubuntu-latest
16
+ environment: ${{{{ inputs.environment }}}}
17
+ steps:
18
+ - name: Configure SSH
19
+ run: |
20
+ mkdir -p ~/.ssh
21
+ echo "${{{{ secrets.COOLIFY_SSH_KEY }}}}" > ~/.ssh/id_ed25519
22
+ chmod 600 ~/.ssh/id_ed25519
23
+ ssh-keyscan -H ${{{{ secrets.COOLIFY_SSH_HOST }}}} >> ~/.ssh/known_hosts
24
+
25
+ - name: Trigger Coolify Deploy (via SSH — the Coolify API is not publicly exposed)
26
+ run: |
27
+ ssh -i ~/.ssh/id_ed25519 ${{{{ secrets.COOLIFY_SSH_USER }}}}@${{{{ secrets.COOLIFY_SSH_HOST }}}} \
28
+ 'T=$(cat ~/.coolify-token); curl -s -X GET -H "Authorization: Bearer $T" "http://localhost:8000/api/v1/deploy?uuid=${{{{ secrets.COOLIFY_APPLICATION_UUID }}}}"'
29
+
30
+ - name: Health Check
31
+ run: |
32
+ sleep 30 # Wait for deployment
33
+ curl --retry 10 --retry-delay 5 --retry-connrefused ${{{{ secrets.APP_URL }}}}/health
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "7.5.0",
3
+ "version": "7.7.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",