@polymorphism-tech/morph-spec 4.17.0 → 4.19.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.
- package/CLAUDE.md +36 -0
- package/README.md +2 -2
- package/bin/task-manager.js +12 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents/architecture/architecture-expert.md +86 -0
- package/framework/agents/architecture/morph-spec-architect.md +66 -0
- package/framework/agents/architecture/packages-validator.md +77 -0
- package/framework/agents/architecture/security-expert.md +67 -0
- package/framework/agents/backend/ms-agent-expert.md +1 -1
- package/framework/agents/backend/neon-expert.md +9 -27
- package/framework/agents/frontend/design-system-validator.md +88 -0
- package/framework/agents/frontend/nextjs-component-validator.md +85 -0
- package/framework/agents/frontend/{ui-ux-designer.md → ui-designer.md} +31 -0
- package/framework/agents/integrations/neon-auth.md +151 -0
- package/framework/agents.json +22 -14
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +39 -0
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +78 -44
- package/framework/hooks/claude-code/post-tool-use/loop-detect.js +103 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +21 -2
- package/framework/hooks/claude-code/stop/validate-completion.js +48 -1
- package/framework/hooks/shared/activity-logger.js +56 -1
- package/framework/hooks/shared/build-check.js +79 -0
- package/framework/hooks/shared/payload-utils.js +39 -0
- package/framework/hooks/shared/session-snapshot.js +105 -0
- package/framework/phases.json +1 -1
- package/framework/rules/neon-auth-standards.md +13 -0
- package/framework/rules/neon-standards.md +1 -0
- package/framework/skills/level-0-meta/mcp-registry.json +34 -1
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +3 -3
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +4 -4
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +11 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +12 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +119 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +13 -9
- package/framework/standards/ai-agents/production.md +5 -2
- package/framework/standards/ai-agents/setup.md +13 -14
- package/framework/standards/ai-agents/workflows.md +8 -6
- package/framework/standards/backend/database/postgresql/database.md +5 -5
- package/framework/standards/backend/database/vector-search-rag.md +1 -1
- package/framework/standards/backend/integrations/asaas/asaas-api.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +364 -0
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +9 -9
- package/framework/templates/README.md +2 -2
- package/framework/templates/REGISTRY.json +5 -5
- package/framework/templates/code/dotnet/jobs/agent.cs +0 -1
- package/framework/templates/code/sql/postgresql-migration.sql +10 -10
- package/framework/templates/code/sql/postgresql-migration.template.sql +12 -14
- package/framework/templates/code/sql/postgresql-rls-policy.sql +6 -9
- package/framework/templates/feature/decisions.md +8 -7
- package/framework/templates/integrations/neon-auth-config.cs +133 -0
- package/framework/workflows/docs/full-morph.md +1 -1
- package/package.json +1 -1
- package/src/commands/mcp/mcp-setup.js +21 -0
- package/src/commands/project/init.js +11 -0
- package/src/lib/contracts/sprint-contract-generator.js +137 -0
- package/src/lib/detectors/index.js +2 -2
- package/src/lib/generators/architecture-scanner.js +2 -3
- package/src/lib/installers/mcp-installer.js +25 -0
- package/src/utils/hooks-installer.js +12 -1
- package/framework/agents/integrations/clerk-auth.md +0 -109
- package/framework/rules/clerk-standards.md +0 -13
- package/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -292
- package/framework/templates/integrations/clerk-config.cs +0 -217
|
@@ -55,6 +55,31 @@ Linear (linear.app), Vercel (vercel.com), Stripe (stripe.com), Dribbble dashboar
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
+
### Stitch MCP (Optional)
|
|
59
|
+
|
|
60
|
+
When Stitch MCP is available, use AI-generated screen designs instead of manual wireframes:
|
|
61
|
+
|
|
62
|
+
| Tool | Purpose |
|
|
63
|
+
|------|---------|
|
|
64
|
+
| `generate_screen_from_text` | Generate screen design from text prompt |
|
|
65
|
+
| `fetch_screen_image` | Export screen as image for review |
|
|
66
|
+
| `fetch_screen_code` | Export design.md with tokens |
|
|
67
|
+
| `list_projects` / `list_screens` | Navigate existing projects |
|
|
68
|
+
|
|
69
|
+
**Companion skills** (auto-installed with Stitch MCP):
|
|
70
|
+
- `stitch-design` — entry point for design generation via Claude Code
|
|
71
|
+
- `stitch-loop` — multi-page website generation
|
|
72
|
+
- `design-md` — generate design.md from existing designs
|
|
73
|
+
- `prompt-enhance` — improve design prompts automatically
|
|
74
|
+
- `react-components` — convert Stitch HTML to React components
|
|
75
|
+
- `shadcn-ui` — guide conversion to ShadCN components (recommended for Next.js projects)
|
|
76
|
+
|
|
77
|
+
**Key:** Stitch MCP transmits real HTML/CSS code (not screenshots), producing dramatically better results.
|
|
78
|
+
|
|
79
|
+
**Rule:** Stitch design.md is NOT a separate deliverable. Extract tokens into `design-system.md`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
58
83
|
## Design Brief Protocol (MANDATORY)
|
|
59
84
|
|
|
60
85
|
> **This protocol runs BEFORE any wireframe, mockup, or visual decision.**
|
|
@@ -74,6 +99,11 @@ Read: .morph/context/design-system.md
|
|
|
74
99
|
- Fill in the missing sections using the Design Thinking questions (Step 1 below)
|
|
75
100
|
- This file is the persistent record — update it, don't just keep it in memory
|
|
76
101
|
|
|
102
|
+
**If Stitch MCP is available:**
|
|
103
|
+
- Check `list_projects` — if a project exists for this feature, its design.md can seed the design system
|
|
104
|
+
- Stitch tokens map INTO `design-system.md` — never a separate file
|
|
105
|
+
- Validate Stitch-exported colors against WCAG AA before adopting
|
|
106
|
+
|
|
77
107
|
**When taking any design decision not covered by the file:**
|
|
78
108
|
1. Add the token/decision to `design-system.md` in the correct section
|
|
79
109
|
2. Register in the Changelog: `- YYYY-MM-DD [ui-ux-designer] Description`
|
|
@@ -208,6 +238,7 @@ Per flow: Trigger, steps, happy path, edge cases, ASCII flowchart diagram.
|
|
|
208
238
|
- [ ] `design-system.md` read (project-level at `.morph/context/`) OR created with real tokens
|
|
209
239
|
- [ ] Design Brief declared before any visual output (direction, palette, motion)
|
|
210
240
|
- [ ] All color values in deliverables use CSS tokens (`--color-accent: #FF4D1C`) not generic names
|
|
241
|
+
- [ ] If Stitch was used: tokens extracted and mapped to `design-system.md` (no separate design.md output)
|
|
211
242
|
- [ ] `mockups.md`: Wireframes for ALL screens + states (loading, error, empty) + responsiveness
|
|
212
243
|
- [ ] `components.md`: shadcn components mapped with props/events/states
|
|
213
244
|
- [ ] `flows.md`: Happy paths + edge cases + ASCII diagrams
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: neon-auth
|
|
3
|
+
description: Neon Auth (Better Auth) specialist for authentication flows, session management, Next.js App Router integration, .NET JWT validation, and RLS policy integration. Use when integrating Neon Auth, implementing social login flows, managing user sessions, or protecting routes with Neon Auth middleware.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
stacks:
|
|
6
|
+
- neon
|
|
7
|
+
color: teal
|
|
8
|
+
effort: medium
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Neon Auth (Better Auth)
|
|
12
|
+
|
|
13
|
+
Authentication with Neon Auth for Next.js and .NET. SDK: `@neondatabase/auth` (Next.js) / JWT Bearer validation (.NET).
|
|
14
|
+
|
|
15
|
+
> **Ref:** `framework/standards/backend/integrations/neon-auth/neon-auth.md`
|
|
16
|
+
|
|
17
|
+
## Setup (Next.js)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// lib/auth/client.ts
|
|
21
|
+
'use client';
|
|
22
|
+
import { createAuthClient } from '@neondatabase/auth/next';
|
|
23
|
+
export const authClient = createAuthClient();
|
|
24
|
+
|
|
25
|
+
// lib/auth/server.ts
|
|
26
|
+
import { createNeonAuth } from '@neondatabase/auth/next/server';
|
|
27
|
+
export const auth = createNeonAuth({
|
|
28
|
+
baseUrl: process.env.NEON_AUTH_BASE_URL!,
|
|
29
|
+
cookies: { secret: process.env.NEON_AUTH_COOKIE_SECRET! },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// app/api/auth/[...path]/route.ts
|
|
33
|
+
import { auth } from '@/lib/auth/server';
|
|
34
|
+
export const { GET, POST } = auth.handler();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Setup (.NET)
|
|
38
|
+
|
|
39
|
+
```csharp
|
|
40
|
+
// Program.cs
|
|
41
|
+
builder.Services.AddAuthentication("NeonAuth")
|
|
42
|
+
.AddJwtBearer("NeonAuth", options =>
|
|
43
|
+
{
|
|
44
|
+
options.Authority = builder.Configuration["NeonAuth:BaseUrl"];
|
|
45
|
+
options.TokenValidationParameters = new()
|
|
46
|
+
{
|
|
47
|
+
ValidateIssuer = true,
|
|
48
|
+
ValidateAudience = false,
|
|
49
|
+
ValidateLifetime = true,
|
|
50
|
+
ValidateIssuerSigningKey = true,
|
|
51
|
+
NameClaimType = "sub"
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
builder.Services.AddAuthorization();
|
|
55
|
+
app.UseAuthentication();
|
|
56
|
+
app.UseAuthorization();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Protected Endpoints
|
|
60
|
+
|
|
61
|
+
```csharp
|
|
62
|
+
// Minimal API
|
|
63
|
+
app.MapGet("/api/profile", (ClaimsPrincipal user) =>
|
|
64
|
+
{
|
|
65
|
+
var userId = user.FindFirstValue("sub");
|
|
66
|
+
if (userId is null) return Results.Unauthorized();
|
|
67
|
+
return Results.Ok(new { UserId = userId });
|
|
68
|
+
}).RequireAuthorization();
|
|
69
|
+
|
|
70
|
+
// Controller: same pattern with [Authorize] + User.FindFirstValue("sub")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Next.js Integration
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// middleware.ts
|
|
77
|
+
import { auth } from '@/lib/auth/server';
|
|
78
|
+
export default auth.middleware({ loginUrl: '/auth/sign-in' });
|
|
79
|
+
export const config = { matcher: ['/((?!.*\\..*|_next|auth).*)', '/'] };
|
|
80
|
+
|
|
81
|
+
// Protected page (server)
|
|
82
|
+
import { auth } from '@/lib/auth/server';
|
|
83
|
+
export const dynamic = 'force-dynamic';
|
|
84
|
+
export default async function DashboardPage() {
|
|
85
|
+
const { data: session } = await auth.getSession();
|
|
86
|
+
if (!session?.user) redirect('/auth/sign-in');
|
|
87
|
+
return <Dashboard user={session.user} />;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Client component
|
|
91
|
+
'use client';
|
|
92
|
+
import { useSession } from '@/lib/auth/client';
|
|
93
|
+
const { data } = useSession();
|
|
94
|
+
const user = data?.user;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Auth Pages
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
// Sign in / Sign up — single component
|
|
101
|
+
import { AuthView } from '@neondatabase/auth/react';
|
|
102
|
+
export default function SignInPage() {
|
|
103
|
+
return <AuthView pathname="sign-in" />;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## OAuth
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
await authClient.signIn.social({
|
|
111
|
+
provider: 'google',
|
|
112
|
+
callbackURL: '/auth/callback',
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## RLS Integration
|
|
117
|
+
|
|
118
|
+
Neon Auth JWTs are validated by the Data API. Use `auth.user_id()` in RLS policies:
|
|
119
|
+
|
|
120
|
+
```sql
|
|
121
|
+
CREATE POLICY "Users own rows"
|
|
122
|
+
ON public.documents FOR ALL TO authenticated
|
|
123
|
+
USING (user_id = auth.user_id());
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Database Schema
|
|
127
|
+
|
|
128
|
+
Auth data lives in `neon_auth` schema (managed automatically):
|
|
129
|
+
- `neon_auth.user` — profiles
|
|
130
|
+
- `neon_auth.session` — sessions
|
|
131
|
+
- `neon_auth.account` — credentials
|
|
132
|
+
|
|
133
|
+
Query directly — no webhook sync needed:
|
|
134
|
+
```sql
|
|
135
|
+
SELECT id, email, name FROM neon_auth.user WHERE id = auth.user_id();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Checklist
|
|
139
|
+
|
|
140
|
+
- [ ] `NEON_AUTH_BASE_URL` in environment variables
|
|
141
|
+
- [ ] `NEON_AUTH_COOKIE_SECRET` (32+ chars) in environment variables
|
|
142
|
+
- [ ] Auth API route at `app/api/auth/[...path]/route.ts`
|
|
143
|
+
- [ ] `NeonAuthUIProvider` wrapping app in root layout
|
|
144
|
+
- [ ] Middleware protecting private routes
|
|
145
|
+
- [ ] `.NET` JWT Bearer configured with Neon Auth authority
|
|
146
|
+
- [ ] RLS policies using `auth.user_id()`
|
|
147
|
+
- [ ] `force-dynamic` on pages using `auth.getSession()`
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
*MORPH-SPEC by Polymorphism Tech*
|
package/framework/agents.json
CHANGED
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"role": "Backend Squad Leader",
|
|
163
163
|
"icon": "⚙️",
|
|
164
164
|
"triggers": ".NET/C# backend, VSA architecture, EF Core, API design, backend squad coordination",
|
|
165
|
-
"spawn_prompt": "You are the Backend Squad Leader. Coordinate backend specialists and own VSA architecture, API design, and .NET/C# standards.\n\nWorkflow:\n1. Read `.morph/state.json` and spec.md for backend requirements\n2. Produce VSA Blueprint: entity fields, operations (slices), routes, error types, validation rules\n3. Delegate to squad: ef-modeler, hangfire-orchestrator, ms-agent-expert, neon-expert, vector-search-expert, asaas-financial,
|
|
165
|
+
"spawn_prompt": "You are the Backend Squad Leader. Coordinate backend specialists and own VSA architecture, API design, and .NET/C# standards.\n\nWorkflow:\n1. Read `.morph/state.json` and spec.md for backend requirements\n2. Produce VSA Blueprint: entity fields, operations (slices), routes, error types, validation rules\n3. Delegate to squad: ef-modeler, hangfire-orchestrator, ms-agent-expert, neon-expert, vector-search-expert, asaas-financial, neon-auth, resend-email\n\nRefer to the Domain Knowledge section below for IHandler, Result<T>, Scrutor, IApiEndpoint patterns and code templates."
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
168
|
"infra-architect": {
|
|
@@ -261,7 +261,7 @@
|
|
|
261
261
|
"role": "UI/UX Squad Leader",
|
|
262
262
|
"icon": "🎨",
|
|
263
263
|
"triggers": "UI/UX design, wireframes, user flows, design system, shadcn/ui component selection",
|
|
264
|
-
"spawn_prompt": "You are the UI/UX Squad Leader. Create wireframes, design system, and user flows during the UI/UX phase.\n\nWorkflow:\n1. Read `.morph/context/design-system.md` FIRST — use only defined tokens\n2. Read spec.md for UI requirements\n3. Generate deliverables: design-system.md, mockups.md, components.md, flows.md\
|
|
264
|
+
"spawn_prompt": "You are the UI/UX Squad Leader. Create wireframes, design system, and user flows during the UI/UX phase.\n\nWorkflow:\n1. Read `.morph/context/design-system.md` FIRST — use only defined tokens\n2. Read spec.md for UI requirements\n3. If Stitch MCP available, offer AI-generated screen designs. Use companion skills (stitch-design, design-md) for workflow. Extract Stitch design.md tokens into design-system.md — no separate output.\n4. Generate deliverables: design-system.md, mockups.md, components.md, flows.md\n5. Coordinate squad: nextjs-expert, css-specialist, seo-growth-hacker\n\nWork is UI/UX phase ONLY — no implementation code. Refer to the Domain Knowledge section below for design brief protocol, shadcn/ui reference, and accessibility rules."
|
|
265
265
|
}
|
|
266
266
|
},
|
|
267
267
|
"ef-modeler": {
|
|
@@ -441,7 +441,7 @@
|
|
|
441
441
|
"role": "Neon PostgreSQL & AI Data Specialist",
|
|
442
442
|
"icon": "\uD83D\uDC18",
|
|
443
443
|
"triggers": "database setup, pgvector, pgrag, vector search, embeddings, Neon branching, RAG pipeline, Semantic Kernel vector store, neonctl",
|
|
444
|
-
"spawn_prompt": "You are the Neon PostgreSQL & AI Data Specialist. Implement database schemas, RLS policies, pgvector embeddings, pgrag RAG pipelines, and manage Neon branches.\n\nWorkflow:\n1. Read spec.md for database, vector search, and RAG requirements\n2. Review architecture from dotnet-senior for entity and schema design\n3. ALWAYS use the pooled connection (-pooler endpoint) for application code — direct connections only for EF Core migrations\n4. Implement RLS policies using auth.user_id()
|
|
444
|
+
"spawn_prompt": "You are the Neon PostgreSQL & AI Data Specialist. Implement database schemas, RLS policies, pgvector embeddings, pgrag RAG pipelines, and manage Neon branches.\n\nWorkflow:\n1. Read spec.md for database, vector search, and RAG requirements\n2. Review architecture from dotnet-senior for entity and schema design\n3. ALWAYS use the pooled connection (-pooler endpoint) for application code — direct connections only for EF Core migrations\n4. Implement RLS policies using auth.user_id() via pg_session_jwt with Neon Auth JWT\n5. Set up pgvector with HNSW indexes and tune maintenance_work_mem before index creation\n6. For RAG pipelines, use pgrag extensions (rag, rag_bge_small_en_v15, rag_jina_reranker_v1_tiny_en) for chunking, embedding, search, and reranking entirely in SQL\n7. Create database branches for PR previews (--schema-only) and run schema-diff before merging\n8. Configure connection retry logic for scale-to-zero cold starts\n\nCRITICAL: Two connection strings — pooled for application, direct for migrations. Never use direct connections in application code.\n\nRefer to the Domain Knowledge section below for connection pooling, RLS patterns, pgvector setup, pgrag pipeline, branching strategy, and scale-to-zero configuration."
|
|
445
445
|
}
|
|
446
446
|
},
|
|
447
447
|
"vector-search-expert": {
|
|
@@ -521,11 +521,11 @@
|
|
|
521
521
|
"spawn_prompt": "You are the Payment Integration Specialist (Asaas/Brazil). Implement PIX, Boleto, Credit Card, and subscription billing.\n\nNever hardcode API keys. Follow contract-compliance patterns.\n\nRefer to the Domain Knowledge section below for Asaas SDK patterns, webhook handling, and payment flow examples."
|
|
522
522
|
}
|
|
523
523
|
},
|
|
524
|
-
"
|
|
524
|
+
"neon-auth": {
|
|
525
525
|
"color": "teal",
|
|
526
526
|
"tier": 3,
|
|
527
|
-
"title": "Auth
|
|
528
|
-
"stacks": ["
|
|
527
|
+
"title": "Neon Auth Integration Specialist",
|
|
528
|
+
"stacks": ["neon"],
|
|
529
529
|
"group": "backend",
|
|
530
530
|
"active_phases": [
|
|
531
531
|
"design",
|
|
@@ -536,21 +536,29 @@
|
|
|
536
536
|
"integration-auth"
|
|
537
537
|
],
|
|
538
538
|
"keywords": [
|
|
539
|
-
"
|
|
540
|
-
"
|
|
539
|
+
"neon auth",
|
|
540
|
+
"better auth",
|
|
541
|
+
"authentication",
|
|
541
542
|
"add auth",
|
|
542
543
|
"implement auth",
|
|
543
544
|
"login",
|
|
544
545
|
"signup",
|
|
545
|
-
"oauth"
|
|
546
|
+
"oauth",
|
|
547
|
+
"session management",
|
|
548
|
+
"neon_auth schema"
|
|
546
549
|
],
|
|
547
550
|
"validators": [],
|
|
548
|
-
"standards": [
|
|
551
|
+
"standards": [
|
|
552
|
+
{
|
|
553
|
+
"id": "neon-auth",
|
|
554
|
+
"scope": "neon"
|
|
555
|
+
}
|
|
556
|
+
],
|
|
549
557
|
"teammate": {
|
|
550
|
-
"role": "Auth
|
|
558
|
+
"role": "Neon Auth Integration Specialist",
|
|
551
559
|
"icon": "🔐",
|
|
552
|
-
"triggers": "
|
|
553
|
-
"spawn_prompt": "You are the Auth
|
|
560
|
+
"triggers": "Neon Auth, authentication, login/signup flows, OAuth, session management, Better Auth",
|
|
561
|
+
"spawn_prompt": "You are the Neon Auth Integration Specialist. Implement authentication flows using Neon Auth (Better Auth) with @neondatabase/auth SDK for Next.js and JWT Bearer validation for .NET.\n\nRefer to the Domain Knowledge section below for SDK setup, auth pages, middleware protection, RLS integration, and .NET JWT configuration."
|
|
554
562
|
}
|
|
555
563
|
},
|
|
556
564
|
"resend-email": {
|
|
@@ -1101,7 +1109,7 @@
|
|
|
1101
1109
|
"neon-expert",
|
|
1102
1110
|
"vector-search-expert",
|
|
1103
1111
|
"asaas-financial",
|
|
1104
|
-
"
|
|
1112
|
+
"neon-auth",
|
|
1105
1113
|
"resend-email"
|
|
1106
1114
|
],
|
|
1107
1115
|
"domains": [
|
|
@@ -69,6 +69,45 @@ async function dispatch(command, payload) {
|
|
|
69
69
|
// Non-blocking
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// Evaluator dispatch: if sprint contract exists, instruct Claude to dispatch evaluator
|
|
73
|
+
try {
|
|
74
|
+
const taskId = taskDoneParsed.taskId;
|
|
75
|
+
if (taskId && !command.includes('--skip-eval')) {
|
|
76
|
+
const contractPath = join(process.cwd(), '.morph', 'features', featureName, '5-implement', 'contracts', `${taskId}-contract.yaml`);
|
|
77
|
+
if (existsSync(contractPath)) {
|
|
78
|
+
const contractContent = readFileSync(contractPath, 'utf-8');
|
|
79
|
+
let changedFiles = '';
|
|
80
|
+
try {
|
|
81
|
+
changedFiles = execSync('git diff --name-only HEAD~1 2>/dev/null | head -10', {
|
|
82
|
+
cwd: process.cwd(), encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
83
|
+
}).trim();
|
|
84
|
+
} catch { /* no git */ }
|
|
85
|
+
|
|
86
|
+
contextParts.push(
|
|
87
|
+
`🔍 EVALUATOR DISPATCH\n` +
|
|
88
|
+
`════════════════════════════════════════\n` +
|
|
89
|
+
`Sprint contract exists for ${taskId}. Dispatch an evaluator sub-agent:\n\n` +
|
|
90
|
+
`Use the Agent tool with:\n` +
|
|
91
|
+
` subagent_type: "general-purpose"\n` +
|
|
92
|
+
` model: "sonnet"\n` +
|
|
93
|
+
` prompt: (see below)\n\n` +
|
|
94
|
+
`--- EVALUATOR PROMPT ---\n` +
|
|
95
|
+
`You are an independent evaluator. Validate this implementation against the sprint contract.\n\n` +
|
|
96
|
+
`## Contract\n${contractContent}\n\n` +
|
|
97
|
+
`## Changed Files\n${changedFiles || '(unable to determine)'}\n\n` +
|
|
98
|
+
`## Instructions\n` +
|
|
99
|
+
`1. Read each changed file\n` +
|
|
100
|
+
`2. Run the verification commands from the contract\n` +
|
|
101
|
+
`3. Evaluate each definitionOfDone item: PASS or FAIL with evidence\n` +
|
|
102
|
+
`4. Return a structured result with overall PASS/FAIL\n` +
|
|
103
|
+
`--- END EVALUATOR PROMPT ---`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// Non-blocking — evaluator is optional
|
|
109
|
+
}
|
|
110
|
+
|
|
72
111
|
// Always inject validator dispatch — deferred if --skip-validation
|
|
73
112
|
const validatorText = await buildValidatorDispatchContext(featureName, payload);
|
|
74
113
|
if (validatorText) {
|
|
@@ -1,44 +1,78 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PostToolUseFailure Hook: Log Tool Failures
|
|
5
|
-
*
|
|
6
|
-
* Event: PostToolUseFailure
|
|
7
|
-
*
|
|
8
|
-
* Runs when any tool fails. Appends a structured JSON entry to
|
|
9
|
-
* .morph/logs/tool-failures.log for debugging and pattern analysis.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostToolUseFailure Hook: Log Tool Failures + Rollback Awareness
|
|
5
|
+
*
|
|
6
|
+
* Event: PostToolUseFailure
|
|
7
|
+
*
|
|
8
|
+
* Runs when any tool fails. Appends a structured JSON entry to
|
|
9
|
+
* .morph/logs/tool-failures.log for debugging and pattern analysis.
|
|
10
|
+
*
|
|
11
|
+
* If a critical failure occurs and there are unstaged changes,
|
|
12
|
+
* suggests git stash as a recovery option.
|
|
13
|
+
*
|
|
14
|
+
* Fail-open: exits 0 on any error.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { appendFileSync, mkdirSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import { execSync } from 'child_process';
|
|
20
|
+
import { readStdin } from '../../shared/stdin-reader.js';
|
|
21
|
+
import { stateExists } from '../../shared/state-reader.js';
|
|
22
|
+
import { injectContext } from '../../shared/hook-response.js';
|
|
23
|
+
import { logHookActivity, logEvent } from '../../shared/activity-logger.js';
|
|
24
|
+
|
|
25
|
+
const CRITICAL_ERROR_PATTERNS = ['ENOENT', 'EPERM', 'EACCES', 'segfault', 'out of memory', 'ENOMEM'];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Only log in morph-spec projects
|
|
29
|
+
if (!stateExists()) {
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const input = await readStdin();
|
|
34
|
+
|
|
35
|
+
const logDir = join(process.cwd(), '.morph', 'logs');
|
|
36
|
+
mkdirSync(logDir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
const entry = {
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
tool: input?.tool_name ?? 'unknown',
|
|
41
|
+
error: input?.error ?? null,
|
|
42
|
+
input: input?.tool_input ?? null,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
appendFileSync(join(logDir, 'tool-failures.log'), JSON.stringify(entry) + '\n');
|
|
46
|
+
logHookActivity('handle-tool-failure', 'PostToolUseFailure', `logged(${entry.tool})`);
|
|
47
|
+
|
|
48
|
+
// Rollback awareness: check for unstaged changes after critical failures
|
|
49
|
+
const errorStr = JSON.stringify(input?.error || '');
|
|
50
|
+
const isCritical = CRITICAL_ERROR_PATTERNS.some(p => errorStr.includes(p));
|
|
51
|
+
|
|
52
|
+
logEvent('tool_failure', { tool: entry.tool, critical: isCritical });
|
|
53
|
+
|
|
54
|
+
if (isCritical) {
|
|
55
|
+
try {
|
|
56
|
+
const gitStatus = execSync('git status --porcelain', {
|
|
57
|
+
cwd: process.cwd(),
|
|
58
|
+
encoding: 'utf-8',
|
|
59
|
+
timeout: 5000,
|
|
60
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
61
|
+
}).trim();
|
|
62
|
+
|
|
63
|
+
if (gitStatus) {
|
|
64
|
+
injectContext(
|
|
65
|
+
`⚠️ [morph-spec] Critical tool failure (${entry.tool}). ` +
|
|
66
|
+
`Unstaged changes detected:\n${gitStatus.split('\n').slice(0, 5).join('\n')}\n\n` +
|
|
67
|
+
`Consider: git stash to save current state before retrying.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// git not available or not a git repo — ignore
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Fail-open: logging is non-critical
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
process.exit(0);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostToolUse Hook: Loop Detection
|
|
5
|
+
*
|
|
6
|
+
* Event: PostToolUse | Matcher: none (all tools)
|
|
7
|
+
*
|
|
8
|
+
* Detects when an agent is stuck in a repetitive loop by tracking
|
|
9
|
+
* the last 15 actions. If the last 8 actions have <= 2 unique
|
|
10
|
+
* signatures, injects a warning with escape instructions.
|
|
11
|
+
*
|
|
12
|
+
* Cooldown: 5 minutes between warnings to prevent meta-loop.
|
|
13
|
+
*
|
|
14
|
+
* Fail-open: exits 0 on any error.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import { readStdin } from '../../shared/stdin-reader.js';
|
|
20
|
+
import { stateExists } from '../../shared/state-reader.js';
|
|
21
|
+
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
22
|
+
import { getCommandSignature } from '../../shared/payload-utils.js';
|
|
23
|
+
import { logEvent } from '../../shared/activity-logger.js';
|
|
24
|
+
|
|
25
|
+
const MAX_BUFFER = 15;
|
|
26
|
+
const LOOP_WINDOW = 8;
|
|
27
|
+
const MAX_UNIQUE_SIGS = 2;
|
|
28
|
+
const COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (!stateExists()) pass();
|
|
32
|
+
|
|
33
|
+
const payload = await readStdin();
|
|
34
|
+
if (!payload) pass();
|
|
35
|
+
|
|
36
|
+
const cwd = process.cwd();
|
|
37
|
+
const memoryDir = join(cwd, '.morph', 'memory');
|
|
38
|
+
const bufferPath = join(memoryDir, 'action-buffer.json');
|
|
39
|
+
const cooldownPath = join(memoryDir, 'loop-cooldown.json');
|
|
40
|
+
|
|
41
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
// Read existing buffer
|
|
44
|
+
let buffer = [];
|
|
45
|
+
try {
|
|
46
|
+
if (existsSync(bufferPath)) {
|
|
47
|
+
buffer = JSON.parse(readFileSync(bufferPath, 'utf-8'));
|
|
48
|
+
if (!Array.isArray(buffer)) buffer = [];
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
buffer = [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Append current action
|
|
55
|
+
const sig = getCommandSignature(payload);
|
|
56
|
+
buffer.push({ sig, ts: new Date().toISOString() });
|
|
57
|
+
|
|
58
|
+
// Trim to max size (FIFO)
|
|
59
|
+
if (buffer.length > MAX_BUFFER) {
|
|
60
|
+
buffer = buffer.slice(-MAX_BUFFER);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Save updated buffer
|
|
64
|
+
writeFileSync(bufferPath, JSON.stringify(buffer), 'utf-8');
|
|
65
|
+
|
|
66
|
+
// Check for loop (need at least LOOP_WINDOW actions)
|
|
67
|
+
if (buffer.length < LOOP_WINDOW) pass();
|
|
68
|
+
|
|
69
|
+
const recent = buffer.slice(-LOOP_WINDOW);
|
|
70
|
+
const uniqueSigs = new Set(recent.map(a => a.sig));
|
|
71
|
+
|
|
72
|
+
if (uniqueSigs.size > MAX_UNIQUE_SIGS) pass();
|
|
73
|
+
|
|
74
|
+
// Check cooldown
|
|
75
|
+
try {
|
|
76
|
+
if (existsSync(cooldownPath)) {
|
|
77
|
+
const cooldown = JSON.parse(readFileSync(cooldownPath, 'utf-8'));
|
|
78
|
+
const elapsed = Date.now() - new Date(cooldown.lastDetected).getTime();
|
|
79
|
+
if (elapsed < COOLDOWN_MS) pass();
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Ignore corrupt cooldown — proceed with detection
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Loop detected — set cooldown and warn
|
|
86
|
+
writeFileSync(cooldownPath, JSON.stringify({ lastDetected: new Date().toISOString() }), 'utf-8');
|
|
87
|
+
|
|
88
|
+
logEvent('loop_detected', { signatures: uniqueSigs.size }, process.cwd());
|
|
89
|
+
|
|
90
|
+
const sigList = [...uniqueSigs].join(', ');
|
|
91
|
+
injectContext(
|
|
92
|
+
`⚠️ [morph-spec] LOOP DETECTED — the last ${LOOP_WINDOW} actions repeat only ${uniqueSigs.size} pattern(s): ${sigList}\n\n` +
|
|
93
|
+
`This usually means you're stuck. Stop and try a different approach:\n` +
|
|
94
|
+
` 1. Re-read the error message carefully — what is it actually saying?\n` +
|
|
95
|
+
` 2. Try a completely different fix strategy\n` +
|
|
96
|
+
` 3. If the test keeps failing, check if the test itself is wrong\n` +
|
|
97
|
+
` 4. If blocked, ask the user for guidance\n\n` +
|
|
98
|
+
`Do NOT continue the same pattern.`
|
|
99
|
+
);
|
|
100
|
+
} catch {
|
|
101
|
+
// Fail-open
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { loadState, getActiveFeature, getPendingGates, getMissingOutputs, derivePhaseForFeature, getTaskStats } from '../../shared/state-reader.js';
|
|
15
|
+
import { loadSessionSnapshot, formatPreviousSession } from '../../shared/session-snapshot.js';
|
|
15
16
|
import { stateExists } from '../../shared/state-reader.js';
|
|
16
17
|
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
17
|
-
import { resetActivity, logHookActivity } from '../../shared/activity-logger.js';
|
|
18
|
+
import { resetActivity, logHookActivity, archiveSession, logEvent } from '../../shared/activity-logger.js';
|
|
18
19
|
import { readFileSync, existsSync, writeFileSync, renameSync, unlinkSync, statSync } from 'fs';
|
|
19
20
|
import { join } from 'path';
|
|
20
21
|
import { resetStaleTasks } from '../../shared/stale-task-reset.js';
|
|
@@ -74,10 +75,14 @@ try {
|
|
|
74
75
|
|
|
75
76
|
const active = getActiveFeature();
|
|
76
77
|
|
|
77
|
-
//
|
|
78
|
+
// Archive previous session log before resetting
|
|
78
79
|
const activeFeatureName = active?.name || '';
|
|
79
80
|
const activePhase = active ? derivePhaseForFeature(active.name) : '';
|
|
81
|
+
archiveSession();
|
|
82
|
+
|
|
83
|
+
// Reset activity log for new session
|
|
80
84
|
resetActivity(new Date().toISOString(), activeFeatureName, activePhase);
|
|
85
|
+
logEvent('session_start', { feature: activeFeatureName, phase: activePhase });
|
|
81
86
|
logHookActivity('inject-morph-context', 'SessionStart', 'ok');
|
|
82
87
|
|
|
83
88
|
// ── Stale task cleanup ──────────────────────────────────────────────────────
|
|
@@ -201,6 +206,20 @@ try {
|
|
|
201
206
|
// Fail-open — worktree context must never block the session
|
|
202
207
|
}
|
|
203
208
|
// ────────────────────────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
// ── Previous session context ──────────────────────────────────────────────
|
|
211
|
+
// Inject summary from previous session for "getting up to speed"
|
|
212
|
+
try {
|
|
213
|
+
const snapshot = loadSessionSnapshot(process.cwd());
|
|
214
|
+
const previousSessionText = formatPreviousSession(snapshot);
|
|
215
|
+
if (previousSessionText) {
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push(previousSessionText);
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
// Fail-open — previous session context must never block
|
|
221
|
+
}
|
|
222
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
204
223
|
} else {
|
|
205
224
|
// Show summary of all features
|
|
206
225
|
const featureNames = Object.keys(state.features);
|