@polymorphism-tech/morph-spec 4.5.0 → 4.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.
- package/CLAUDE.md +77 -56
- package/README.md +394 -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/{skills/level-2-domains → agents}/README.md +14 -14
- package/framework/{skills/level-2-domains → agents}/ai-agents/ai-system-architect.md +1 -4
- package/framework/{skills/level-2-domains → agents}/architecture/po-pm-advisor.md +1 -2
- package/framework/{skills/level-2-domains → agents}/architecture/prompt-engineer.md +1 -2
- package/framework/{skills/level-2-domains → agents}/architecture/seo-growth-hacker.md +1 -2
- package/framework/{skills/level-2-domains → agents}/architecture/standards-architect.md +159 -162
- package/framework/agents/backend/api-designer.md +103 -0
- package/framework/{skills/level-2-domains → agents}/backend/dotnet-senior.md +1 -2
- package/framework/agents/backend/ef-modeler.md +119 -0
- package/framework/{skills/level-2-domains → agents}/backend/hangfire-orchestrator.md +1 -4
- package/framework/{skills/level-2-domains → agents}/backend/ms-agent-expert.md +1 -4
- package/framework/{skills/level-2-domains → agents}/frontend/blazor-builder.md +1 -4
- package/framework/agents/frontend/nextjs-expert.md +118 -0
- package/framework/{skills/level-2-domains → agents}/frontend/ui-ux-designer.md +1 -2
- package/framework/{skills/level-2-domains → agents}/infrastructure/azure-architect.md +147 -148
- package/framework/{skills/level-2-domains → agents}/infrastructure/azure-deploy-specialist.md +1 -2
- package/framework/{skills/level-2-domains → agents}/infrastructure/bicep-architect.md +1 -4
- package/framework/{skills/level-2-domains → agents}/infrastructure/container-specialist.md +1 -4
- package/framework/{skills/level-2-domains → agents}/infrastructure/devops-engineer.md +1 -4
- package/framework/agents/infrastructure/infra-architect.md +45 -0
- package/framework/{skills/level-2-domains → agents}/integrations/asaas-financial.md +1 -4
- package/framework/{skills/level-2-domains → agents}/integrations/azure-identity.md +1 -4
- package/framework/{skills/level-2-domains → agents}/integrations/clerk-auth.md +1 -4
- package/framework/{skills/level-2-domains → agents}/integrations/hangfire-integration.md +1 -2
- package/framework/{skills/level-2-domains → agents}/integrations/resend-email.md +1 -4
- package/framework/{skills/level-2-domains → agents}/quality/code-analyzer.md +1 -4
- package/framework/{skills/level-2-domains → agents}/quality/testing-specialist.md +1 -4
- package/framework/agents.json +1145 -278
- package/framework/hooks/claude-code/statusline.py +384 -85
- package/framework/hooks/shared/phase-utils.js +129 -129
- package/framework/rules/frontend-standards.md +0 -3
- package/framework/rules/nextjs-standards.md +17 -0
- package/framework/skills/README.md +66 -0
- package/framework/skills/level-0-meta/{brainstorming.md → brainstorming/SKILL.md} +3 -1
- package/framework/skills/level-0-meta/brainstorming/references/proposal-example.md +138 -0
- package/framework/skills/level-0-meta/{code-review.md → code-review/SKILL.md} +3 -2
- package/framework/skills/level-0-meta/code-review/references/review-example.md +164 -0
- package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +121 -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/{morph-checklist.md → morph-checklist/SKILL.md} +2 -5
- package/framework/skills/{level-1-workflows/morph-replicate.md → level-0-meta/morph-replicate/SKILL.md} +6 -7
- package/framework/skills/level-0-meta/{simulation-checklist.md → simulation-checklist/SKILL.md} +3 -6
- package/framework/skills/level-0-meta/{tool-usage-guide.md → tool-usage-guide/SKILL.md} +4 -5
- package/framework/skills/level-0-meta/{verification-before-completion.md → verification-before-completion/SKILL.md} +3 -1
- package/framework/skills/level-0-meta/verification-before-completion/scripts/check-phase-outputs.mjs +110 -0
- package/framework/skills/level-1-workflows/{phase-clarify.md → phase-clarify/SKILL.md} +3 -3
- package/framework/skills/level-1-workflows/phase-clarify/references/clarifications-example.md +117 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis.md → phase-codebase-analysis/SKILL.md} +2 -3
- package/framework/skills/level-1-workflows/{phase-design.md → phase-design/SKILL.md} +46 -182
- package/framework/skills/level-1-workflows/phase-design/references/spec-example.md +253 -0
- package/framework/skills/level-1-workflows/{phase-implement.md → phase-implement/SKILL.md} +3 -3
- package/framework/skills/level-1-workflows/phase-implement/references/recap-example.md +132 -0
- package/framework/skills/level-1-workflows/{phase-setup.md → phase-setup/SKILL.md} +2 -3
- package/framework/skills/level-1-workflows/{phase-tasks.md → phase-tasks/SKILL.md} +42 -3
- package/framework/skills/level-1-workflows/phase-tasks/references/tasks-example.md +231 -0
- package/framework/skills/level-1-workflows/phase-tasks/scripts/validate-tasks.mjs +112 -0
- package/framework/skills/level-1-workflows/{phase-uiux.md → phase-uiux/SKILL.md} +2 -3
- 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 +19 -65
- package/src/commands/project/update.js +7 -63
- package/src/lib/detectors/claude-config-detector.js +1 -3
- 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 +16 -4
- package/src/utils/skills-installer.js +59 -15
- package/.morph/.morphversion +0 -5
- package/.morph/analytics/threads-log.jsonl +0 -44
- package/.morph/config/config.json +0 -8
- package/.morph/context/README.md +0 -17
- 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 -198
- 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/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 -51
- package/.morph/memory/pre-compact-2026-02-22T17-01-01-658Z.json +0 -16
- package/.morph/state.json +0 -48
- package/framework/skills/level-2-domains/backend/api-designer.md +0 -66
- package/framework/skills/level-2-domains/backend/ef-modeler.md +0 -65
- package/framework/skills/level-2-domains/frontend/nextjs-expert.md +0 -161
- package/framework/skills/level-3-technologies/README.md +0 -7
- package/framework/skills/level-4-patterns/README.md +0 -7
- 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;
|
|
@@ -4,7 +4,6 @@ import ora from 'ora';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import {
|
|
7
|
-
getContentDir,
|
|
8
7
|
copyDirectory,
|
|
9
8
|
copyFile,
|
|
10
9
|
pathExists,
|
|
@@ -13,9 +12,7 @@ import {
|
|
|
13
12
|
ensureDir,
|
|
14
13
|
writeFile,
|
|
15
14
|
readFile,
|
|
16
|
-
updateGitignore
|
|
17
|
-
createSymlink,
|
|
18
|
-
createDirectoryLink
|
|
15
|
+
updateGitignore
|
|
19
16
|
} from '../../utils/file-copier.js';
|
|
20
17
|
import { saveProjectMorphVersion, getInstalledCLIVersion } from '../../utils/version-checker.js';
|
|
21
18
|
import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
|
|
@@ -122,7 +119,6 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
122
119
|
await writeFile(contextReadme, readmeContent);
|
|
123
120
|
|
|
124
121
|
// 5. Copy framework templates (project-local overrides start from framework defaults)
|
|
125
|
-
const contentDir = getContentDir();
|
|
126
122
|
const frameworkTemplatesSrc = join(import.meta.dirname, '..', '..', '..', 'framework', 'templates');
|
|
127
123
|
const templatesDest = join(frameworkDestDir, 'templates');
|
|
128
124
|
let templatesCopied = false;
|
|
@@ -151,14 +147,12 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
151
147
|
await copyFile(agentsSrc, agentsDest);
|
|
152
148
|
}
|
|
153
149
|
|
|
154
|
-
// 8. Copy .claude commands and
|
|
150
|
+
// 8. Copy .claude commands and install skills
|
|
155
151
|
// Source: framework/ (canonical for all stacks)
|
|
156
152
|
spinner.text = 'Setting up Claude Code integration...';
|
|
157
153
|
const frameworkDir = join(import.meta.dirname, '..', '..', '..', 'framework');
|
|
158
154
|
const claudeDest = join(targetPath, '.claude');
|
|
159
155
|
|
|
160
|
-
let symlinkCount = 0;
|
|
161
|
-
let copyCount = 0;
|
|
162
156
|
let commandsCopied = false;
|
|
163
157
|
|
|
164
158
|
// Copy commands directory (slash commands): framework/commands/ → .claude/commands/
|
|
@@ -171,55 +165,6 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
171
165
|
logger.warn(' ⚠ framework/commands/ source missing — commands not installed');
|
|
172
166
|
}
|
|
173
167
|
|
|
174
|
-
// Create directory links for skill categories (or copy if linking fails)
|
|
175
|
-
// Source: framework/skills/ → .claude/skills/
|
|
176
|
-
{
|
|
177
|
-
const skillsSrc = join(frameworkDir, 'skills');
|
|
178
|
-
const skillsDest = join(claudeDest, 'skills');
|
|
179
|
-
|
|
180
|
-
if (await pathExists(skillsSrc)) {
|
|
181
|
-
await ensureDir(skillsDest);
|
|
182
|
-
|
|
183
|
-
const entries = await fs.readdir(skillsSrc, { withFileTypes: true });
|
|
184
|
-
|
|
185
|
-
let linkedCategories = 0;
|
|
186
|
-
let copiedCategories = 0;
|
|
187
|
-
let totalSkillFiles = 0;
|
|
188
|
-
|
|
189
|
-
// Link category directories (specialists/, infra/, checklists/, etc.)
|
|
190
|
-
for (const entry of entries) {
|
|
191
|
-
if (entry.isDirectory()) {
|
|
192
|
-
const categorySrc = join(skillsSrc, entry.name);
|
|
193
|
-
const categoryDest = join(skillsDest, entry.name);
|
|
194
|
-
|
|
195
|
-
// Count .md files for reporting
|
|
196
|
-
const categoryEntries = await fs.readdir(categorySrc, { withFileTypes: true });
|
|
197
|
-
const mdFiles = categoryEntries.filter(e => e.isFile() && e.name.endsWith('.md'));
|
|
198
|
-
totalSkillFiles += mdFiles.length;
|
|
199
|
-
|
|
200
|
-
const result = await createDirectoryLink(categorySrc, categoryDest);
|
|
201
|
-
if (result === 'copy') {
|
|
202
|
-
copiedCategories++;
|
|
203
|
-
} else {
|
|
204
|
-
linkedCategories++;
|
|
205
|
-
}
|
|
206
|
-
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
207
|
-
// Handle .md files in skills root
|
|
208
|
-
totalSkillFiles++;
|
|
209
|
-
const result = await createSymlink(join(skillsSrc, entry.name), join(skillsDest, entry.name), 'file');
|
|
210
|
-
if (result === 'symlink') {
|
|
211
|
-
linkedCategories++;
|
|
212
|
-
} else {
|
|
213
|
-
copiedCategories++;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
symlinkCount = linkedCategories > 0 ? totalSkillFiles : 0;
|
|
219
|
-
copyCount = linkedCategories === 0 ? totalSkillFiles : 0;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
168
|
// 9a. Install path-scoped rules to .claude/rules/ (native Claude Code pattern)
|
|
224
169
|
spinner.text = 'Installing path-scoped rules to .claude/rules/...';
|
|
225
170
|
const rulesSrc = join(frameworkDir, 'rules');
|
|
@@ -236,7 +181,8 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
236
181
|
|
|
237
182
|
// 9b2. Install tier-1/2 agents as native Claude Code subagents in .claude/agents/
|
|
238
183
|
spinner.text = 'Installing native subagents to .claude/agents/...';
|
|
239
|
-
|
|
184
|
+
const detectedStackForAgents = config.project.stack ?? null;
|
|
185
|
+
await installAgents(targetPath, frameworkDir, { projectStack: detectedStackForAgents });
|
|
240
186
|
|
|
241
187
|
// 9b2b. Install level-2 domain agents as native Claude Code subagents in .claude/agents/
|
|
242
188
|
await installDomainAgents(targetPath, frameworkDir);
|
|
@@ -341,6 +287,19 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
341
287
|
|
|
342
288
|
if (structure?.stack && structure.stack !== 'unknown') {
|
|
343
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
|
+
|
|
344
303
|
spinner.stop();
|
|
345
304
|
logger.blank();
|
|
346
305
|
logger.header('Existing Project Detected');
|
|
@@ -542,13 +501,8 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
542
501
|
}
|
|
543
502
|
}
|
|
544
503
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
logger.dim(` ✓ .claude/skills/ (${symlinkCount} ${linkType})`);
|
|
548
|
-
} else if (copyCount > 0) {
|
|
549
|
-
logger.dim(` ✓ .claude/skills/ (${copyCount} copied)`);
|
|
550
|
-
logger.warn(` ⚠ Directory links not supported (copied instead). Skills won't auto-update.`);
|
|
551
|
-
}
|
|
504
|
+
logger.dim(' ✓ .claude/skills/ (installed as skill directories)');
|
|
505
|
+
logger.dim(' ✓ .claude/agents/ (native subagents installed)');
|
|
552
506
|
|
|
553
507
|
logger.blank();
|
|
554
508
|
|
|
@@ -14,8 +14,6 @@ import {
|
|
|
14
14
|
copyFile,
|
|
15
15
|
pathExists,
|
|
16
16
|
ensureDir,
|
|
17
|
-
createDirectoryLink,
|
|
18
|
-
createSymlink,
|
|
19
17
|
readJson,
|
|
20
18
|
writeJson
|
|
21
19
|
} from '../../utils/file-copier.js';
|
|
@@ -239,8 +237,6 @@ export async function updateCommand(options) {
|
|
|
239
237
|
const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
|
|
240
238
|
const claudeDest = join(targetPath, '.claude');
|
|
241
239
|
|
|
242
|
-
let symlinkCount = 0;
|
|
243
|
-
let copyCount = 0;
|
|
244
240
|
let commandsCopied = false;
|
|
245
241
|
|
|
246
242
|
// Copy commands directory (slash commands): framework/commands/ → .claude/commands/
|
|
@@ -253,62 +249,18 @@ export async function updateCommand(options) {
|
|
|
253
249
|
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
254
250
|
}
|
|
255
251
|
|
|
256
|
-
// Create directory links for skill categories (or copy if linking fails)
|
|
257
|
-
// Source: framework/skills/ → .claude/skills/
|
|
258
|
-
{
|
|
259
|
-
const skillsSrc = join(frameworkDir, 'skills');
|
|
260
|
-
const skillsDest = join(claudeDest, 'skills');
|
|
261
|
-
|
|
262
|
-
if (await pathExists(skillsSrc)) {
|
|
263
|
-
await ensureDir(skillsDest);
|
|
264
|
-
|
|
265
|
-
const entries = await fs.readdir(skillsSrc, { withFileTypes: true });
|
|
266
|
-
|
|
267
|
-
let linkedCategories = 0;
|
|
268
|
-
let copiedCategories = 0;
|
|
269
|
-
let totalSkillFiles = 0;
|
|
270
|
-
|
|
271
|
-
// Link category directories (specialists/, infra/, checklists/, etc.)
|
|
272
|
-
for (const entry of entries) {
|
|
273
|
-
if (entry.isDirectory()) {
|
|
274
|
-
const categorySrc = join(skillsSrc, entry.name);
|
|
275
|
-
const categoryDest = join(skillsDest, entry.name);
|
|
276
|
-
|
|
277
|
-
// Count .md files for reporting
|
|
278
|
-
const categoryEntries = await fs.readdir(categorySrc, { withFileTypes: true });
|
|
279
|
-
const mdFiles = categoryEntries.filter(e => e.isFile() && e.name.endsWith('.md'));
|
|
280
|
-
totalSkillFiles += mdFiles.length;
|
|
281
|
-
|
|
282
|
-
const result = await createDirectoryLink(categorySrc, categoryDest);
|
|
283
|
-
if (result === 'copy') {
|
|
284
|
-
copiedCategories++;
|
|
285
|
-
} else {
|
|
286
|
-
linkedCategories++;
|
|
287
|
-
}
|
|
288
|
-
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
289
|
-
// Handle .md files in skills root
|
|
290
|
-
totalSkillFiles++;
|
|
291
|
-
const result = await createSymlink(join(skillsSrc, entry.name), join(skillsDest, entry.name), 'file');
|
|
292
|
-
if (result === 'symlink') {
|
|
293
|
-
linkedCategories++;
|
|
294
|
-
} else {
|
|
295
|
-
copiedCategories++;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
symlinkCount = linkedCategories > 0 ? totalSkillFiles : 0;
|
|
301
|
-
copyCount = linkedCategories === 0 ? totalSkillFiles : 0;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
252
|
// Sync morph skills to .claude/skills/ for native Claude Code discovery
|
|
306
253
|
updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
|
|
307
254
|
await installSkills(targetPath);
|
|
308
255
|
|
|
309
256
|
// Sync native subagents to .claude/agents/
|
|
310
257
|
updateSpinner.text = 'Syncing agents to .claude/agents/...';
|
|
311
|
-
|
|
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 });
|
|
312
264
|
await installDomainAgents(targetPath, frameworkDir);
|
|
313
265
|
|
|
314
266
|
// Sync path-scoped rules to .claude/rules/
|
|
@@ -365,16 +317,8 @@ export async function updateCommand(options) {
|
|
|
365
317
|
logger.dim(' ✓ .morph/framework/agents.json');
|
|
366
318
|
if (commandsCopied) logger.dim(' ✓ .claude/commands/');
|
|
367
319
|
|
|
368
|
-
if (symlinkCount > 0) {
|
|
369
|
-
const linkType = process.platform === 'win32' ? 'junction-linked' : 'symlinked';
|
|
370
|
-
logger.dim(` ✓ .claude/skills/ (${symlinkCount} ${linkType})`);
|
|
371
|
-
} else if (copyCount > 0) {
|
|
372
|
-
logger.dim(` ✓ .claude/skills/ (${copyCount} copied)`);
|
|
373
|
-
logger.warn(` ⚠ Directory links not supported (copied instead). Skills won't auto-update.`);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
320
|
logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
|
|
377
|
-
logger.dim(' ✓ .claude/skills/ (
|
|
321
|
+
logger.dim(' ✓ .claude/skills/ (skills installed as directories)');
|
|
378
322
|
logger.dim(' ✓ .claude/agents/ (native subagents refreshed)');
|
|
379
323
|
logger.dim(' ✓ .claude/rules/ (path-scoped rules synced)');
|
|
380
324
|
logger.dim(' ✓ .claude/CLAUDE.md (runtime quick reference)');
|
|
@@ -144,9 +144,7 @@ function detectCustomSkills(globalClaudeDir, projectClaudeDir) {
|
|
|
144
144
|
for (const file of walkMdFiles(projectSkills)) {
|
|
145
145
|
const name = extractSkillName(file);
|
|
146
146
|
// Skip morph-spec framework skills (they're managed by init)
|
|
147
|
-
if (file.includes('level-0-meta') || file.includes('level-1-workflows')
|
|
148
|
-
file.includes('level-2-domains') || file.includes('level-3-technologies') ||
|
|
149
|
-
file.includes('level-4-patterns')) {
|
|
147
|
+
if (file.includes('level-0-meta') || file.includes('level-1-workflows')) {
|
|
150
148
|
continue;
|
|
151
149
|
}
|
|
152
150
|
skills.push({
|
|
@@ -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
|
+
}
|