@polymorphism-tech/morph-spec 4.6.0 → 4.7.1
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/README.md +414 -700
- package/docs/ARCHITECTURE.md +331 -0
- package/docs/CHEATSHEET.md +221 -0
- package/docs/COMMAND-FLOWS.md +368 -0
- package/docs/QUICKSTART.md +212 -0
- package/docs/examples/order-management/contracts.cs +84 -0
- package/docs/examples/order-management/proposal.md +24 -0
- package/docs/examples/order-management/spec.md +162 -0
- package/docs/plans/2026-02-23-ddd-architecture-refactor.md +1153 -0
- package/docs/plans/2026-02-23-ddd-nextsteps.md +682 -0
- package/docs/plans/2026-02-23-infra-architect-refactor.md +437 -0
- package/docs/plans/2026-02-23-nextjs-code-review-design.md +156 -0
- package/docs/plans/2026-02-23-nextjs-code-review-impl.md +1254 -0
- package/docs/plans/2026-02-23-nextjs-standards-design.md +149 -0
- package/docs/plans/2026-02-23-nextjs-standards-impl.md +1846 -0
- package/framework/agents/README.md +14 -14
- package/framework/agents/architecture/standards-architect.md +159 -159
- package/framework/agents/frontend/nextjs-expert.md +87 -127
- package/framework/agents/infrastructure/azure-architect.md +147 -147
- package/framework/agents/infrastructure/infra-architect.md +45 -0
- package/framework/agents.json +1145 -278
- package/framework/rules/frontend-standards.md +0 -3
- package/framework/rules/nextjs-standards.md +17 -0
- package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +147 -0
- package/framework/skills/level-0-meta/code-review-nextjs/references/review-example-nextjs.md +254 -0
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +45 -9
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +38 -0
- package/framework/standards/STANDARDS.json +121 -0
- package/framework/standards/architecture/ddd/bounded-contexts.md +105 -0
- package/framework/standards/architecture/ddd/complexity-levels.md +108 -0
- package/framework/standards/architecture/ddd/ubiquitous-language.md +58 -0
- package/framework/standards/frontend/nextjs/app-router.md +123 -0
- package/framework/standards/frontend/nextjs/components.md +132 -0
- package/framework/standards/frontend/nextjs/data-fetching.md +126 -0
- package/framework/standards/frontend/nextjs/forms.md +128 -0
- package/framework/standards/frontend/nextjs/naming-conventions.md +67 -0
- package/framework/standards/frontend/nextjs/project-structure.md +102 -0
- package/framework/standards/frontend/nextjs/state-management.md +72 -0
- package/framework/standards/frontend/nextjs/testing.md +111 -0
- package/framework/templates/REGISTRY.json +538 -142
- package/framework/templates/code/dotnet/contracts/contracts-level1.cs +69 -0
- package/framework/templates/code/dotnet/contracts/contracts-level2.cs +86 -0
- package/framework/templates/code/dotnet/contracts/contracts-level3.cs +41 -0
- package/framework/templates/docs/spec.md +49 -0
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -0
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -0
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -0
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -0
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -0
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -0
- package/framework/templates/project-structure/dotnet-ddd.md +70 -0
- package/framework/workflows/docs/enforcement-pipeline.md +2 -1
- package/package.json +1 -1
- package/scripts/scan-nextjs.mjs +169 -0
- package/src/commands/project/doctor.js +52 -1
- package/src/commands/project/init.js +15 -1
- package/src/commands/project/update.js +6 -1
- package/src/lib/standards/standards-context-injector.js +5 -0
- package/src/lib/validators/nextjs/index.js +6 -0
- package/src/lib/validators/nextjs/next-component-validator.js +181 -0
- package/src/lib/validators/validation-runner.js +5 -0
- package/src/utils/agents-installer.js +14 -2
- package/.morph/.morphversion +0 -5
- package/.morph/analytics/threads-log.jsonl +0 -6
- package/.morph/config/config.json +0 -8
- package/.morph/framework/agents.json +0 -948
- package/.morph/framework/standards/STANDARDS.json +0 -812
- package/.morph/framework/standards/ai-agents/blazor-ui.md +0 -364
- package/.morph/framework/standards/ai-agents/production.md +0 -415
- package/.morph/framework/standards/ai-agents/setup.md +0 -418
- package/.morph/framework/standards/ai-agents/team-orchestration.md +0 -479
- package/.morph/framework/standards/ai-agents/workflows.md +0 -354
- package/.morph/framework/standards/architecture/ddd/aggregates.md +0 -120
- package/.morph/framework/standards/architecture/ddd/entities.md +0 -99
- package/.morph/framework/standards/architecture/ddd/value-objects.md +0 -124
- package/.morph/framework/standards/backend/api/minimal-api.md +0 -494
- package/.morph/framework/standards/backend/api/rest.md +0 -492
- package/.morph/framework/standards/backend/api/validation.md +0 -88
- package/.morph/framework/standards/backend/authentication/passkeys.md +0 -428
- package/.morph/framework/standards/backend/database/ef-core.md +0 -199
- package/.morph/framework/standards/backend/database/migrations.md +0 -393
- package/.morph/framework/standards/backend/database/postgresql/database.md +0 -352
- package/.morph/framework/standards/backend/database/repository-patterns.md +0 -528
- package/.morph/framework/standards/backend/database/vector-search-rag.md +0 -541
- package/.morph/framework/standards/backend/dotnet/async.md +0 -366
- package/.morph/framework/standards/backend/dotnet/core.md +0 -117
- package/.morph/framework/standards/backend/dotnet/di.md +0 -439
- package/.morph/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
- package/.morph/framework/standards/backend/integrations/asaas/asaas-api.md +0 -216
- package/.morph/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -290
- package/.morph/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +0 -350
- package/.morph/framework/standards/backend/integrations/resend/resend-email.md +0 -385
- package/.morph/framework/standards/context/analytics.md +0 -96
- package/.morph/framework/standards/context/bundles.md +0 -110
- package/.morph/framework/standards/context/priming.md +0 -78
- package/.morph/framework/standards/core/architecture.md +0 -185
- package/.morph/framework/standards/core/coding.md +0 -214
- package/.morph/framework/standards/core/git-branching-strategy.md +0 -403
- package/.morph/framework/standards/core/git.md +0 -185
- package/.morph/framework/standards/core/testing.md +0 -295
- package/.morph/framework/standards/data/nosql/blob-storage.md +0 -102
- package/.morph/framework/standards/data/nosql/cache/redis.md +0 -97
- package/.morph/framework/standards/data/nosql/cosmos-db.md +0 -118
- package/.morph/framework/standards/data/vector-search/azure-ai-search.md +0 -121
- package/.morph/framework/standards/data/vector-search/rag-chunking.md +0 -104
- package/.morph/framework/standards/frontend/blazor/design-checklist.md +0 -222
- package/.morph/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
- package/.morph/framework/standards/frontend/blazor/fluent-ui.md +0 -137
- package/.morph/framework/standards/frontend/blazor/html-conversion.md +0 -184
- package/.morph/framework/standards/frontend/blazor/lifecycle.md +0 -195
- package/.morph/framework/standards/frontend/blazor/pitfalls.md +0 -198
- package/.morph/framework/standards/frontend/blazor/state.md +0 -191
- package/.morph/framework/standards/frontend/design-system/animations.md +0 -151
- package/.morph/framework/standards/frontend/design-system/naming.md +0 -64
- package/.morph/framework/standards/frontend/nextjs/nextjs-patterns.md +0 -215
- package/.morph/framework/standards/infrastructure/azure/azure.md +0 -624
- package/.morph/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
- package/.morph/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
- package/.morph/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
- package/.morph/framework/standards/infrastructure/azure/services/functions.md +0 -486
- package/.morph/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
- package/.morph/framework/standards/infrastructure/azure/services/storage.md +0 -407
- package/.morph/framework/standards/infrastructure/docker/easypanel-deploy.md +0 -196
- package/.morph/framework/standards/infrastructure/supabase/mcp-setup.md +0 -252
- package/.morph/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
- package/.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
- package/.morph/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
- package/.morph/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
- package/.morph/framework/standards/integration/api/graphql.md +0 -91
- package/.morph/framework/standards/integration/api/grpc.md +0 -114
- package/.morph/framework/standards/integration/api/rest-design.md +0 -95
- package/.morph/framework/standards/integration/event-driven/cqrs.md +0 -101
- package/.morph/framework/standards/integration/event-driven/event-sourcing.md +0 -124
- package/.morph/framework/standards/integration/event-driven/service-bus.md +0 -95
- package/.morph/framework/standards/integration/mcp/mcp-tools.md +0 -384
- package/.morph/framework/standards/observability/logging.md +0 -131
- package/.morph/framework/standards/observability/metrics.md +0 -121
- package/.morph/framework/standards/observability/monitoring.md +0 -114
- package/.morph/framework/standards/observability/tracing.md +0 -132
- package/.morph/framework/standards/workflows/parallel-execution.md +0 -112
- package/.morph/framework/standards/workflows/thread-management.md +0 -113
- package/.morph/framework/templates/.idea/morph-templates.xml +0 -92
- package/.morph/framework/templates/.vscode/morph-templates.code-snippets +0 -186
- package/.morph/framework/templates/IDE-SNIPPETS.md +0 -266
- package/.morph/framework/templates/README.md +0 -814
- package/.morph/framework/templates/REGISTRY.json +0 -1492
- package/.morph/framework/templates/code/dotnet/backend/repository.cs +0 -141
- package/.morph/framework/templates/code/dotnet/backend/service.cs +0 -139
- package/.morph/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
- package/.morph/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
- package/.morph/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
- package/.morph/framework/templates/code/dotnet/contracts/README.md +0 -74
- package/.morph/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
- package/.morph/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
- package/.morph/framework/templates/code/dotnet/contracts/contracts.cs.hbs +0 -172
- package/.morph/framework/templates/code/dotnet/database/migration.cs +0 -83
- package/.morph/framework/templates/code/dotnet/frontend/component.razor +0 -239
- package/.morph/framework/templates/code/dotnet/jobs/agent.cs +0 -163
- package/.morph/framework/templates/code/dotnet/jobs/job.cs +0 -171
- package/.morph/framework/templates/code/dotnet/test.cs +0 -239
- package/.morph/framework/templates/code/sql/rls-policy.sql +0 -57
- package/.morph/framework/templates/code/sql/supabase-migration.sql +0 -100
- package/.morph/framework/templates/code/sql/supabase-migration.template.sql +0 -113
- package/.morph/framework/templates/code/typescript/contracts.ts +0 -168
- package/.morph/framework/templates/context/CONTEXT-FEATURE.md +0 -276
- package/.morph/framework/templates/context/CONTEXT.md +0 -181
- package/.morph/framework/templates/docs/clarifications.md +0 -253
- package/.morph/framework/templates/docs/onboarding.md +0 -123
- package/.morph/framework/templates/docs/proposal.md +0 -182
- package/.morph/framework/templates/docs/schema-analysis.md +0 -119
- package/.morph/framework/templates/docs/spec.md +0 -149
- package/.morph/framework/templates/docs/ui-components.md +0 -124
- package/.morph/framework/templates/docs/ui-design-system.md +0 -76
- package/.morph/framework/templates/docs/ui-flows.md +0 -167
- package/.morph/framework/templates/docs/ui-mockups.md +0 -98
- package/.morph/framework/templates/docs/user-stories.md +0 -34
- package/.morph/framework/templates/examples/design-system-examples.md +0 -357
- package/.morph/framework/templates/examples/spec-examples.md +0 -90
- package/.morph/framework/templates/feature/decisions.md +0 -187
- package/.morph/framework/templates/feature/recap.md +0 -146
- package/.morph/framework/templates/feature/tasks.md +0 -199
- package/.morph/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
- package/.morph/framework/templates/infrastructure/azure/README.md +0 -286
- package/.morph/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
- package/.morph/framework/templates/infrastructure/azure/app-service.bicep +0 -164
- package/.morph/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
- package/.morph/framework/templates/infrastructure/azure/container-app.bicep +0 -156
- package/.morph/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
- package/.morph/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
- package/.morph/framework/templates/infrastructure/azure/deploy.sh +0 -208
- package/.morph/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
- package/.morph/framework/templates/infrastructure/azure/main.bicep +0 -189
- package/.morph/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
- package/.morph/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
- package/.morph/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
- package/.morph/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
- package/.morph/framework/templates/infrastructure/azure/storage.bicep +0 -106
- package/.morph/framework/templates/infrastructure/docker/Dockerfile.template +0 -58
- package/.morph/framework/templates/infrastructure/docker/docker-compose.template.yml +0 -67
- package/.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +0 -38
- package/.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +0 -48
- package/.morph/framework/templates/infrastructure/docker/easypanel.template.json +0 -54
- package/.morph/framework/templates/infrastructure/github/README.md +0 -593
- package/.morph/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
- package/.morph/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +0 -45
- package/.morph/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +0 -27
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
- package/.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +0 -59
- package/.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +0 -39
- package/.morph/framework/templates/integrations/asaas-client.cs +0 -387
- package/.morph/framework/templates/integrations/asaas-webhook.cs +0 -351
- package/.morph/framework/templates/integrations/azure-identity-config.cs +0 -288
- package/.morph/framework/templates/integrations/clerk-config.cs +0 -258
- package/.morph/framework/templates/meta-prompts/fusion/fusion-agent.md +0 -76
- package/.morph/framework/templates/meta-prompts/fusion/fusion-aggregator.md +0 -100
- package/.morph/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/.morph/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/.morph/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
- package/.morph/framework/templates/meta-prompts/squad-leaders/backend-squad.md +0 -90
- package/.morph/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +0 -126
- package/.morph/framework/templates/meta-prompts/squad-leaders/squad-leader.md +0 -43
- package/.morph/framework/templates/meta-prompts/validators/checkpoint-validator.md +0 -107
- package/.morph/framework/templates/meta-prompts/validators/pre-commit-validator.md +0 -95
- package/.morph/framework/templates/saas/subscription.cs +0 -347
- package/.morph/framework/templates/saas/tenant.cs +0 -338
- package/.morph/framework/templates/state.template.json +0 -17
- package/.morph/framework/templates/ui/FluentDesignTheme.cs +0 -149
- package/.morph/framework/templates/ui/MudTheme.cs +0 -281
- package/.morph/framework/templates/ui/design-system.css +0 -226
- package/.morph/logs/tool-failures.log +0 -7
- package/.morph/memory/pre-compact-2026-02-23T15-43-03-521Z.json +0 -16
- package/.morph/state.json +0 -48
- package/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
- package/framework/templates/code/dotnet/contracts/contracts.cs.hbs +0 -172
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scan-nextjs.mjs - CLI scan for Next.js violations.
|
|
4
|
+
* Usage: node scripts/scan-nextjs.mjs [path] [--json]
|
|
5
|
+
* Exit: 0=clean, 1=errors, 2=scan failed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { join, resolve } from 'node:path';
|
|
10
|
+
import { validateNextComponent } from '../src/lib/validators/nextjs/next-component-validator.js';
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const jsonMode = args.includes('--json');
|
|
14
|
+
const pathArg = args.find(a => !a.startsWith('--'));
|
|
15
|
+
const targetPath = pathArg ? resolve(pathArg) : resolve('src');
|
|
16
|
+
|
|
17
|
+
function checkUseEffectFetch(content, filePath) {
|
|
18
|
+
const issues = [];
|
|
19
|
+
if (/useEffect\s*\(\s*(?:async\s+)?\(\s*\)\s*=>/.test(content) && /\bfetch\s*\(/.test(content)) {
|
|
20
|
+
issues.push({
|
|
21
|
+
type: 'error',
|
|
22
|
+
message: "useEffect used for data fetching — use Server Components or TanStack Query (useQuery) instead",
|
|
23
|
+
suggestion: "Replace with a Server Component fetch or TanStack Query useQuery hook",
|
|
24
|
+
file: filePath,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return issues;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function checkDefaultExport(content, filePath) {
|
|
31
|
+
const issues = [];
|
|
32
|
+
const fileName = filePath.split('/').pop() ?? '';
|
|
33
|
+
const SPECIAL = ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx', 'not-found.tsx'];
|
|
34
|
+
if (!SPECIAL.includes(fileName) && /^export\s+default\s+/m.test(content) && fileName.endsWith('.tsx')) {
|
|
35
|
+
issues.push({
|
|
36
|
+
type: 'warning',
|
|
37
|
+
message: `Default export in '${fileName}' — prefer named exports for easier refactoring`,
|
|
38
|
+
suggestion: "Change to: export function ComponentName() {}",
|
|
39
|
+
file: filePath,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return issues;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findTsxFiles(dir) {
|
|
46
|
+
const results = [];
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
50
|
+
} catch {
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const fullPath = join(dir, entry.name);
|
|
55
|
+
if (['node_modules', '.next', 'dist', '.git'].includes(entry.name)) continue;
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
results.push(...findTsxFiles(fullPath));
|
|
58
|
+
} else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
|
|
59
|
+
results.push(fullPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
function main() {
|
|
65
|
+
if (!existsSync(targetPath)) {
|
|
66
|
+
const msg = `Error: Path does not exist: ${targetPath}`;
|
|
67
|
+
if (jsonMode) {
|
|
68
|
+
console.log(JSON.stringify({ error: msg, exitCode: 2 }));
|
|
69
|
+
} else {
|
|
70
|
+
process.stderr.write(msg + '\n');
|
|
71
|
+
}
|
|
72
|
+
process.exit(2);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let files;
|
|
76
|
+
try {
|
|
77
|
+
files = findTsxFiles(targetPath);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const msg = `Error scanning directory: ${err.message}`;
|
|
80
|
+
if (jsonMode) {
|
|
81
|
+
console.log(JSON.stringify({ error: msg, exitCode: 2 }));
|
|
82
|
+
} else {
|
|
83
|
+
process.stderr.write(msg + '\n');
|
|
84
|
+
}
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const normalizedTarget = targetPath.replace(/\\/g, '/');
|
|
89
|
+
|
|
90
|
+
const allIssues = [];
|
|
91
|
+
for (const filePath of files) {
|
|
92
|
+
let content;
|
|
93
|
+
try {
|
|
94
|
+
content = readFileSync(filePath, 'utf8');
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const normalizedFile = filePath.replace(/\\/g, '/');
|
|
99
|
+
const relPath = normalizedFile.startsWith(normalizedTarget)
|
|
100
|
+
? normalizedFile.slice(normalizedTarget.length).replace(/^\//, '')
|
|
101
|
+
: normalizedFile;
|
|
102
|
+
|
|
103
|
+
const issues = [
|
|
104
|
+
...validateNextComponent(content, relPath),
|
|
105
|
+
...checkUseEffectFetch(content, relPath),
|
|
106
|
+
...checkDefaultExport(content, relPath),
|
|
107
|
+
];
|
|
108
|
+
allIssues.push(...issues);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const errors = allIssues.filter(i => i.type === 'error');
|
|
112
|
+
const warnings = allIssues.filter(i => i.type === 'warning');
|
|
113
|
+
|
|
114
|
+
if (jsonMode) {
|
|
115
|
+
const out = JSON.stringify({
|
|
116
|
+
files: files.length,
|
|
117
|
+
issues: allIssues.map(i => ({
|
|
118
|
+
severity: i.type,
|
|
119
|
+
message: i.message,
|
|
120
|
+
file: i.file,
|
|
121
|
+
line: i.line ?? null,
|
|
122
|
+
suggestion: i.suggestion ?? null,
|
|
123
|
+
})),
|
|
124
|
+
errors: errors.length,
|
|
125
|
+
warnings: warnings.length,
|
|
126
|
+
exitCode: errors.length > 0 ? 1 : 0,
|
|
127
|
+
}, null, 2);
|
|
128
|
+
process.stdout.write(out + '\n');
|
|
129
|
+
process.exit(errors.length > 0 ? 1 : 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const displayPath = targetPath.replace(process.cwd().replace(/\\/g, '/'), '.').replace(/\\/g, '/');
|
|
133
|
+
console.log(`🔍 Scanning ${displayPath} (${files.length} file${files.length !== 1 ? "s" : ""})...`);
|
|
134
|
+
console.log("");
|
|
135
|
+
|
|
136
|
+
if (allIssues.length === 0) {
|
|
137
|
+
console.log(`✅ No issues found — ${files.length} file${files.length !== 1 ? "s" : ""} scanned`);
|
|
138
|
+
console.log(`Summary: ${files.length} files scanned | 0 issues (0 critical, 0 high, 0 medium)`);
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (errors.length > 0) {
|
|
143
|
+
console.log(`CRITICAL (${errors.length})`);
|
|
144
|
+
for (const issue of errors) {
|
|
145
|
+
const loc = issue.line ? `:${issue.line}` : "";
|
|
146
|
+
console.log(` ${issue.file}${loc} — ${issue.message}`);
|
|
147
|
+
if (issue.suggestion) console.log(` → ${issue.suggestion}`);
|
|
148
|
+
}
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (warnings.length > 0) {
|
|
153
|
+
console.log(`HIGH/MEDIUM (${warnings.length})`);
|
|
154
|
+
for (const issue of warnings) {
|
|
155
|
+
console.log(` ${issue.file} — ${issue.message}`);
|
|
156
|
+
}
|
|
157
|
+
console.log('');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const cleanFiles = files.length - new Set(allIssues.map(i => i.file)).size;
|
|
161
|
+
if (cleanFiles > 0) {
|
|
162
|
+
console.log(`✅ No issues found in ${cleanFiles} file${cleanFiles !== 1 ? "s" : ""}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(`Summary: ${files.length} files scanned | ${allIssues.length} issue${allIssues.length !== 1 ? "s" : ""} (${errors.length} critical, ${warnings.length} high/medium)`);
|
|
166
|
+
process.exit(errors.length > 0 ? 1 : 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main();
|
|
@@ -411,7 +411,58 @@ export async function doctorCommand(options = {}) {
|
|
|
411
411
|
// Check agents.json
|
|
412
412
|
const agentsPath = join(morphPath, 'framework', 'agents.json');
|
|
413
413
|
if (await pathExists(agentsPath)) {
|
|
414
|
-
|
|
414
|
+
try {
|
|
415
|
+
const agentsData = JSON.parse(await fs.readFile(agentsPath, 'utf8'));
|
|
416
|
+
const agentEntries = Object.entries(agentsData.agents || {})
|
|
417
|
+
.filter(([k]) => !k.startsWith('_comment'));
|
|
418
|
+
const actualCount = agentEntries.length;
|
|
419
|
+
const declaredCount = agentsData.total_agents;
|
|
420
|
+
|
|
421
|
+
// Check 1: total_agents matches actual count
|
|
422
|
+
if (actualCount !== declaredCount) {
|
|
423
|
+
checks.push({
|
|
424
|
+
name: 'agents.json count',
|
|
425
|
+
status: 'warn',
|
|
426
|
+
msg: `total_agents declares ${declaredCount} but found ${actualCount} agents`
|
|
427
|
+
});
|
|
428
|
+
hasWarnings = true;
|
|
429
|
+
} else {
|
|
430
|
+
checks.push({ name: 'agents.json count', status: 'ok', msg: `${actualCount} agents` });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Check 2: required tier-2 agents present
|
|
434
|
+
const requiredTier2 = ['standards-architect', 'dotnet-senior', 'infra-architect', 'domain-architect'];
|
|
435
|
+
const missingTier2 = requiredTier2.filter(id => !agentsData.agents[id]);
|
|
436
|
+
if (missingTier2.length > 0) {
|
|
437
|
+
checks.push({
|
|
438
|
+
name: 'agents.json required agents',
|
|
439
|
+
status: 'error',
|
|
440
|
+
msg: `Missing required agents: ${missingTier2.join(', ')}`
|
|
441
|
+
});
|
|
442
|
+
hasErrors = true;
|
|
443
|
+
} else {
|
|
444
|
+
checks.push({ name: 'agents.json required agents', status: 'ok' });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Check 3: tier counts sum matches total
|
|
448
|
+
const tierCounts = [1, 2, 3, 4].map(t =>
|
|
449
|
+
agentEntries.filter(([, v]) => v.tier === t).length
|
|
450
|
+
);
|
|
451
|
+
const tierSum = tierCounts.reduce((a, b) => a + b, 0);
|
|
452
|
+
if (tierSum !== actualCount) {
|
|
453
|
+
checks.push({
|
|
454
|
+
name: 'agents.json tier counts',
|
|
455
|
+
status: 'warn',
|
|
456
|
+
msg: `Tier counts sum to ${tierSum} but total is ${actualCount}`
|
|
457
|
+
});
|
|
458
|
+
hasWarnings = true;
|
|
459
|
+
} else {
|
|
460
|
+
checks.push({ name: 'agents.json tier counts', status: 'ok' });
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
checks.push({ name: 'agents.json', status: 'error', msg: 'invalid JSON' });
|
|
464
|
+
hasErrors = true;
|
|
465
|
+
}
|
|
415
466
|
} else {
|
|
416
467
|
checks.push({ name: 'agents.json', status: 'missing' });
|
|
417
468
|
hasErrors = true;
|
|
@@ -181,7 +181,8 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
181
181
|
|
|
182
182
|
// 9b2. Install tier-1/2 agents as native Claude Code subagents in .claude/agents/
|
|
183
183
|
spinner.text = 'Installing native subagents to .claude/agents/...';
|
|
184
|
-
|
|
184
|
+
const detectedStackForAgents = config.project.stack ?? null;
|
|
185
|
+
await installAgents(targetPath, frameworkDir, { projectStack: detectedStackForAgents });
|
|
185
186
|
|
|
186
187
|
// 9b2b. Install level-2 domain agents as native Claude Code subagents in .claude/agents/
|
|
187
188
|
await installDomainAgents(targetPath, frameworkDir);
|
|
@@ -286,6 +287,19 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
286
287
|
|
|
287
288
|
if (structure?.stack && structure.stack !== 'unknown') {
|
|
288
289
|
detectedStack = structure.stack;
|
|
290
|
+
|
|
291
|
+
// Persist detected stack to config.json
|
|
292
|
+
if (structure?.stack && structure.stack !== 'unknown') {
|
|
293
|
+
config.project.stack = structure.stack;
|
|
294
|
+
}
|
|
295
|
+
if (structure?.architecture && structure.architecture !== 'unknown') {
|
|
296
|
+
config.project.architecture = structure.architecture;
|
|
297
|
+
}
|
|
298
|
+
if (structure?.uiLibrary) {
|
|
299
|
+
config.project.uiLibrary = structure.uiLibrary;
|
|
300
|
+
}
|
|
301
|
+
await writeJson(configPath, config);
|
|
302
|
+
|
|
289
303
|
spinner.stop();
|
|
290
304
|
logger.blank();
|
|
291
305
|
logger.header('Existing Project Detected');
|
|
@@ -255,7 +255,12 @@ export async function updateCommand(options) {
|
|
|
255
255
|
|
|
256
256
|
// Sync native subagents to .claude/agents/
|
|
257
257
|
updateSpinner.text = 'Syncing agents to .claude/agents/...';
|
|
258
|
-
|
|
258
|
+
let projectStack = null;
|
|
259
|
+
try {
|
|
260
|
+
const cfg = await readJson(join(morphPath, 'config', 'config.json'));
|
|
261
|
+
projectStack = cfg?.project?.stack ?? null;
|
|
262
|
+
} catch { /* ignore — config may not exist yet */ }
|
|
263
|
+
await installAgents(targetPath, frameworkDir, { projectStack });
|
|
259
264
|
await installDomainAgents(targetPath, frameworkDir);
|
|
260
265
|
|
|
261
266
|
// Sync path-scoped rules to .claude/rules/
|
|
@@ -44,6 +44,11 @@ const AGENT_STANDARDS_MAP = {
|
|
|
44
44
|
'backend/database/ef-core',
|
|
45
45
|
'backend/api/rest'
|
|
46
46
|
],
|
|
47
|
+
'infra-architect': [
|
|
48
|
+
'core/architecture',
|
|
49
|
+
'core/coding'
|
|
50
|
+
],
|
|
51
|
+
// Tier 3: Specialists - Infrastructure Squad
|
|
47
52
|
'azure-architect': [
|
|
48
53
|
'infrastructure/azure/azure', // Stack-specific
|
|
49
54
|
'core/architecture',
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Next.js Component Validator
|
|
7
|
+
*
|
|
8
|
+
* Validates .tsx files for:
|
|
9
|
+
* 1. Unnecessary 'use client' directives (no interactivity)
|
|
10
|
+
* 2. Missing 'use client' when React hooks are used
|
|
11
|
+
* 3. File naming convention (kebab-case)
|
|
12
|
+
*
|
|
13
|
+
* @module next-component-validator
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ============================================
|
|
17
|
+
// CONSTANTS
|
|
18
|
+
// ============================================
|
|
19
|
+
|
|
20
|
+
/** Hooks/patterns that require 'use client' */
|
|
21
|
+
const CLIENT_HOOKS = ['useState', 'useEffect', 'useRef', 'useReducer', 'useCallback', 'useMemo', 'useContext'];
|
|
22
|
+
|
|
23
|
+
/** Event handler patterns that indicate interactivity (hooks are checked via CLIENT_HOOKS) */
|
|
24
|
+
const EVENT_HANDLER_PATTERNS = [
|
|
25
|
+
/\bonClick\b/,
|
|
26
|
+
/\bonChange\b/,
|
|
27
|
+
/\bonSubmit\b/,
|
|
28
|
+
/\bonFocus\b/,
|
|
29
|
+
/\bonBlur\b/,
|
|
30
|
+
/\bonKeyDown\b/,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** Next.js special files exempt from naming checks */
|
|
34
|
+
const NEXTJS_SPECIAL_FILES = ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx', 'not-found.tsx', 'route.ts', 'middleware.ts'];
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// MAIN VALIDATION FUNCTION
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} ValidationIssue
|
|
42
|
+
* @property {'error' | 'warning' | 'info'} type
|
|
43
|
+
* @property {string} message
|
|
44
|
+
* @property {string} [suggestion]
|
|
45
|
+
* @property {string} file
|
|
46
|
+
* @property {number} [line]
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validates a Next.js component file.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} content - File content
|
|
53
|
+
* @param {string} filePath - Relative file path
|
|
54
|
+
* @returns {ValidationIssue[]}
|
|
55
|
+
*/
|
|
56
|
+
export function validateNextComponent(content, filePath) {
|
|
57
|
+
const issues = [];
|
|
58
|
+
|
|
59
|
+
// Only validate .tsx and .ts files
|
|
60
|
+
if (!filePath.endsWith('.tsx') && !filePath.endsWith('.ts')) {
|
|
61
|
+
return issues;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Skip non-component utility files (no JSX, no hooks)
|
|
65
|
+
const hasJsx = /(?<![a-zA-Z0-9])<[A-Z][a-zA-Z]*[\s/>]|(?<![a-zA-Z0-9])<[a-z][a-z]+[\s>/]/.test(content);
|
|
66
|
+
const hasHooks = CLIENT_HOOKS.some(hook => content.includes(hook));
|
|
67
|
+
|
|
68
|
+
if (!hasJsx && !hasHooks) {
|
|
69
|
+
return issues;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const hasUseClient = /^'use client';?|^"use client";?/.test(content.trimStart());
|
|
73
|
+
|
|
74
|
+
// Check 1: use client present but no interactivity
|
|
75
|
+
if (hasUseClient) {
|
|
76
|
+
const hasEventHandlers = EVENT_HANDLER_PATTERNS.some(pattern => pattern.test(content));
|
|
77
|
+
const hasClientHooks = CLIENT_HOOKS.some(hook => new RegExp(`\\b${hook}\\b`).test(content));
|
|
78
|
+
if (!hasEventHandlers && !hasClientHooks) {
|
|
79
|
+
issues.push({
|
|
80
|
+
type: 'warning',
|
|
81
|
+
message: "'use client' directive present but no interactivity detected (no hooks, no event handlers)",
|
|
82
|
+
suggestion: "Remove 'use client' to make this a Server Component, or add interactive behavior",
|
|
83
|
+
file: filePath,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check 2: hooks used without use client
|
|
89
|
+
if (!hasUseClient) {
|
|
90
|
+
const usedHooks = CLIENT_HOOKS.filter(hook => new RegExp(`\\b${hook}\\b`).test(content));
|
|
91
|
+
if (usedHooks.length > 0) {
|
|
92
|
+
issues.push({
|
|
93
|
+
type: 'error',
|
|
94
|
+
message: `React hook(s) used (${usedHooks.join(', ')}) without 'use client' directive`,
|
|
95
|
+
suggestion: "Add 'use client' as the first line of the file",
|
|
96
|
+
file: filePath,
|
|
97
|
+
line: 1,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check 3: file naming convention (kebab-case)
|
|
103
|
+
const fileName = filePath.split('/').pop() ?? '';
|
|
104
|
+
const isSpecialFile = NEXTJS_SPECIAL_FILES.includes(fileName);
|
|
105
|
+
|
|
106
|
+
if (!isSpecialFile && fileName.endsWith('.tsx')) {
|
|
107
|
+
const baseName = fileName.replace('.tsx', '');
|
|
108
|
+
const hasUpperCase = /[A-Z]/.test(baseName);
|
|
109
|
+
if (hasUpperCase) {
|
|
110
|
+
issues.push({
|
|
111
|
+
type: 'warning',
|
|
112
|
+
message: `Component file '${fileName}' should use kebab-case naming`,
|
|
113
|
+
suggestion: `Rename to '${toKebabCase(baseName)}.tsx'`,
|
|
114
|
+
file: filePath,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return issues;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================
|
|
123
|
+
// HELPERS
|
|
124
|
+
// ============================================
|
|
125
|
+
|
|
126
|
+
function toKebabCase(str) {
|
|
127
|
+
return str
|
|
128
|
+
.replace(/([A-Z])/g, '-$1')
|
|
129
|
+
.toLowerCase()
|
|
130
|
+
.replace(/^-/, '');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// PROJECT-LEVEL SCANNER
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Validates all Next.js component files in a project directory.
|
|
139
|
+
* Wraps validateNextComponent for use in validation-runner.js.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} projectPath - Root path of the project
|
|
142
|
+
* @param {Object} _options - Validator options (unused, for interface compatibility)
|
|
143
|
+
* @returns {Promise<{ errors: number, warnings: number, issues: Array }>}
|
|
144
|
+
*/
|
|
145
|
+
export async function validateNextComponentFiles(projectPath, _options = {}) {
|
|
146
|
+
const result = { errors: 0, warnings: 0, issues: [] };
|
|
147
|
+
|
|
148
|
+
// Find all .tsx and .ts files under src/
|
|
149
|
+
const srcDir = join(projectPath, 'src');
|
|
150
|
+
const normalizedSrc = srcDir.replace(/\\/g, '/');
|
|
151
|
+
const pattern = normalizedSrc + '/**/*.{tsx,ts}';
|
|
152
|
+
const files = await glob(pattern, { ignore: ['**/node_modules/**', '**/.next/**'] });
|
|
153
|
+
|
|
154
|
+
for (const filePath of files) {
|
|
155
|
+
let content;
|
|
156
|
+
try {
|
|
157
|
+
content = readFileSync(filePath, 'utf8');
|
|
158
|
+
} catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Make path relative for cleaner output
|
|
163
|
+
const normFilePath = filePath.replace(/\\/g, '/');
|
|
164
|
+
const relPath = normFilePath.replace(projectPath.replace(/\\/g, '/') + '/', '');
|
|
165
|
+
const fileIssues = validateNextComponent(content, relPath);
|
|
166
|
+
|
|
167
|
+
for (const issue of fileIssues) {
|
|
168
|
+
result.issues.push({
|
|
169
|
+
level: issue.type,
|
|
170
|
+
message: issue.message,
|
|
171
|
+
file: issue.file,
|
|
172
|
+
line: issue.line,
|
|
173
|
+
solution: issue.suggestion,
|
|
174
|
+
});
|
|
175
|
+
if (issue.type === 'error') result.errors++;
|
|
176
|
+
else if (issue.type === 'warning') result.warnings++;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
@@ -216,6 +216,11 @@ async function runSingleValidator(validatorId, projectPath, featureName, options
|
|
|
216
216
|
return await validateDesignSystem(projectPath, featureName, options);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
case 'nextjs-component': {
|
|
220
|
+
const { validateNextComponentFiles } = await import('./nextjs/next-component-validator.js');
|
|
221
|
+
return await validateNextComponentFiles(projectPath, options);
|
|
222
|
+
}
|
|
223
|
+
|
|
219
224
|
default:
|
|
220
225
|
if (options.verbose) {
|
|
221
226
|
console.log(chalk.gray(` Unknown validator: ${validatorId}`));
|
|
@@ -17,8 +17,12 @@ import { join } from 'path';
|
|
|
17
17
|
*
|
|
18
18
|
* @param {string} projectDir - Target project directory
|
|
19
19
|
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
20
|
+
* @param {object} options - Optional settings
|
|
21
|
+
* @param {string|null} options.projectStack - Detected project stack (e.g. 'nextjs', 'blazor')
|
|
20
22
|
*/
|
|
21
|
-
export async function installAgents(projectDir, frameworkDir = 'framework') {
|
|
23
|
+
export async function installAgents(projectDir, frameworkDir = 'framework', options = {}) {
|
|
24
|
+
const { projectStack = null } = options;
|
|
25
|
+
|
|
22
26
|
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
23
27
|
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
|
|
24
28
|
|
|
@@ -33,7 +37,15 @@ export async function installAgents(projectDir, frameworkDir = 'framework') {
|
|
|
33
37
|
const targetDir = join(projectDir, '.claude', 'agents');
|
|
34
38
|
mkdirSync(targetDir, { recursive: true });
|
|
35
39
|
|
|
36
|
-
const
|
|
40
|
+
const DOTNET_STACKS = ['dotnet', 'blazor', 'dotnet-api', 'fullstack'];
|
|
41
|
+
const eligible = agents.filter(a => {
|
|
42
|
+
if (a.tier !== 1 && a.tier !== 2) return false;
|
|
43
|
+
// Skip dotnet-senior for non-.NET projects
|
|
44
|
+
if (a.id === 'dotnet-senior' && projectStack && !DOTNET_STACKS.includes(projectStack)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
});
|
|
37
49
|
|
|
38
50
|
for (const agent of eligible) {
|
|
39
51
|
const slug = agent.id ?? agent.name?.toLowerCase().replace(/\s+/g, '-');
|
package/.morph/.morphversion
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{"type":"fusion_started","feature":"test-feature","data":{"count":3,"strategy":"best-of-n","agents":["fusion-agent-1","fusion-agent-2","fusion-agent-3"]},"timestamp":"2026-02-23T15:31:40.102Z"}
|
|
2
|
-
{"type":"fusion_started","feature":"test-feature","data":{"count":3,"strategy":"best-of-n","agents":["fusion-agent-1","fusion-agent-2","fusion-agent-3"]},"timestamp":"2026-02-23T15:45:29.404Z"}
|
|
3
|
-
{"type":"fusion_started","feature":"test-feature","data":{"count":3,"strategy":"best-of-n","agents":["fusion-agent-1","fusion-agent-2","fusion-agent-3"]},"timestamp":"2026-02-23T15:46:05.207Z"}
|
|
4
|
-
{"type":"fusion_started","feature":"test-feature","data":{"count":3,"strategy":"best-of-n","agents":["fusion-agent-1","fusion-agent-2","fusion-agent-3"]},"timestamp":"2026-02-23T16:05:33.457Z"}
|
|
5
|
-
{"type":"fusion_started","feature":"test-feature","data":{"count":3,"strategy":"best-of-n","agents":["fusion-agent-1","fusion-agent-2","fusion-agent-3"]},"timestamp":"2026-02-23T16:14:14.564Z"}
|
|
6
|
-
{"type":"fusion_started","feature":"test-feature","data":{"count":3,"strategy":"best-of-n","agents":["fusion-agent-1","fusion-agent-2","fusion-agent-3"]},"timestamp":"2026-02-23T16:31:49.191Z"}
|