@polymorphism-tech/morph-spec 4.12.0 → 4.14.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 +7 -7
- package/README.md +28 -34
- package/bin/morph-spec.js +41 -24
- package/bin/task-manager.js +962 -1341
- package/bin/validate.js +1 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +14 -16
- package/docs/QUICKSTART.md +15 -15
- package/framework/CLAUDE.md +5 -2
- package/framework/agents/README.md +3 -4
- package/framework/agents/ai-agents/ai-system-architect.md +6 -10
- package/framework/agents/architecture/seo-growth-hacker.md +166 -326
- package/framework/agents/architecture/standards-architect.md +21 -31
- package/framework/agents/backend/dotnet-senior.md +160 -86
- package/framework/agents/backend/ef-modeler.md +2 -6
- package/framework/agents/backend/hangfire-orchestrator.md +8 -9
- package/framework/agents/backend/ms-agent-expert.md +1 -1
- package/framework/agents/frontend/nextjs-expert.md +198 -133
- package/framework/agents/frontend/ui-ux-designer.md +226 -267
- package/framework/agents/infrastructure/infra-architect.md +191 -27
- package/framework/agents/integrations/clerk-auth.md +19 -26
- package/framework/agents/integrations/hangfire-integration.md +1 -4
- package/framework/agents.json +1084 -2278
- package/framework/commands/morph-apply.md +4 -6
- package/framework/commands/morph-preflight.md +192 -269
- package/framework/commands/morph-proposal.md +210 -238
- package/framework/commands/morph-status.md +72 -92
- package/framework/commands/morph-troubleshoot.md +6 -13
- package/framework/hooks/README.md +1 -6
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
- package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
- package/framework/hooks/claude-code/statusline.py +4 -5
- package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
- package/framework/hooks/shared/compact-restore.js +28 -5
- package/framework/hooks/shared/phase-utils.js +4 -8
- package/framework/hooks/shared/state-reader.js +34 -3
- package/framework/phases.json +162 -200
- package/framework/rules/ai-agents-standards.md +15 -0
- package/framework/rules/asaas-standards.md +13 -0
- package/framework/rules/clerk-standards.md +13 -0
- package/framework/rules/csharp-standards.md +8 -4
- package/framework/rules/data-standards.md +18 -0
- package/framework/rules/dotnet-templates.md +87 -0
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/hangfire-standards.md +13 -0
- package/framework/rules/infrastructure-standards.md +27 -16
- package/framework/rules/morph-workflow.md +73 -13
- package/framework/rules/nextjs-standards.md +1 -0
- package/framework/rules/nextjs-templates.md +40 -0
- package/framework/rules/resend-standards.md +12 -0
- package/framework/rules/supabase-standards.md +29 -0
- package/framework/skills/README.md +6 -8
- package/framework/skills/level-0-meta/mcp-registry.json +21 -99
- package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
- package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
- package/framework/skills/level-1-workflows/README.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +57 -123
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +143 -172
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +57 -77
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/squad-templates/backend-only.json +6 -6
- package/framework/squad-templates/frontend-only.json +17 -8
- package/framework/squad-templates/full-stack.json +29 -24
- package/framework/standards/STANDARDS.json +1021 -2369
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
- package/framework/standards/backend/dotnet/result-pattern.md +213 -0
- package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
- package/framework/standards/backend/dotnet/scrutor.md +158 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
- package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
- package/framework/standards/integration/mcp/mcp-tools.md +91 -77
- package/framework/templates/IDE-SNIPPETS.md +2 -6
- package/framework/templates/README.md +709 -814
- package/framework/templates/REGISTRY.json +223 -762
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
- package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
- package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
- package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
- package/framework/templates/code/dotnet/backend/repository.cs +148 -141
- package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
- package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
- package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
- package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
- package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
- package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
- package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
- package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
- package/framework/templates/code/dotnet/jobs/job.cs +21 -17
- package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
- package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
- package/framework/templates/code/dotnet/test.cs +192 -106
- package/framework/templates/context/ARCHITECTURE.md +89 -0
- package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
- package/framework/templates/context/CONTEXT.md +180 -181
- package/framework/templates/context/design-system.md +1 -5
- package/framework/templates/docs/onboarding.md +2 -3
- package/framework/templates/docs/proposal.md +171 -182
- package/framework/templates/docs/spec.md +1 -3
- package/framework/templates/docs/ui-components.md +0 -72
- package/framework/templates/examples/design-system-examples.md +4 -4
- package/framework/templates/feature/decisions.md +0 -66
- package/framework/templates/feature/recap.md +1 -2
- package/framework/templates/infrastructure/github/README.md +56 -593
- package/framework/templates/integrations/clerk-config.cs +0 -41
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
- package/framework/templates/project/validate.js +2 -4
- package/framework/templates/ui/design-system.css +1 -1
- package/framework/workflows/README.md +8 -8
- package/framework/workflows/configs/fast-track.json +51 -0
- package/framework/workflows/configs/full.json +68 -0
- package/framework/workflows/configs/standard.json +7 -3
- package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
- package/framework/workflows/docs/enforcement-pipeline.md +18 -19
- package/framework/workflows/docs/full-morph.md +69 -76
- package/framework/workflows/docs/standard.md +49 -44
- package/framework/workflows/docs/ui-refresh.md +1 -1
- package/package.json +3 -3
- package/src/commands/agents/dispatch-agents.js +55 -116
- package/src/commands/context/context-refresh.js +79 -0
- package/src/commands/phase/phase-reset.js +1 -1
- package/src/commands/project/doctor.js +10 -32
- package/src/commands/project/init.js +12 -0
- package/src/commands/project/status.js +6 -12
- package/src/commands/scaffold/scaffold.js +131 -0
- package/src/commands/scope/escalate.js +1 -1
- package/src/commands/state/advance-phase.js +15 -29
- package/src/commands/state/state.js +20 -5
- package/src/commands/tasks/task.js +0 -30
- package/src/commands/validation/validate-feature.js +3 -10
- package/src/core/paths/output-schema.js +12 -13
- package/src/core/state/phase-state-machine.js +211 -217
- package/src/core/state/state-manager.js +9 -9
- package/src/lib/agents/agents-manager.js +2 -2
- package/src/lib/agents/output-collector.js +1 -2
- package/src/lib/detectors/design-system-detector.js +4 -4
- package/src/lib/detectors/index.js +1 -11
- package/src/lib/generators/architecture-scanner.js +640 -0
- package/src/lib/generators/context-generator.js +3 -3
- package/src/lib/generators/design-system-generator.js +508 -587
- package/src/lib/generators/index.js +12 -10
- package/src/lib/generators/metadata-extractor.js +386 -387
- package/src/lib/generators/recap-generator.js +4 -4
- package/src/lib/generators/scaffold-generator.js +317 -0
- package/src/lib/generators/settings-generator.js +0 -1
- package/src/lib/phase-chain/phase-validator.js +64 -42
- package/src/lib/stack/stack-profile.js +2 -2
- package/src/lib/standards/digest-builder.js +72 -285
- package/src/lib/tasks/task-parser.js +11 -5
- package/src/lib/validators/architecture/architecture-validator.js +259 -387
- package/src/lib/validators/content/content-validator.js +287 -287
- package/src/lib/validators/css/css-validator.js +340 -352
- package/src/lib/validators/packages/package-validator.js +347 -360
- package/src/lib/validators/shared/issue-counter.js +14 -18
- package/src/lib/validators/validation-runner.js +3 -19
- package/src/scripts/setup-infra.js +1 -1
- package/src/utils/agents-installer.js +61 -10
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +11 -19
- package/src/utils/skills-installer.js +178 -178
- package/framework/agents/architecture/prompt-engineer.md +0 -195
- package/framework/agents/backend/api-designer.md +0 -106
- package/framework/agents/frontend/blazor-builder.md +0 -229
- package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
- package/framework/agents/infrastructure/azure-architect.md +0 -150
- package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
- package/framework/agents/infrastructure/bicep-architect.md +0 -132
- package/framework/agents/infrastructure/container-specialist.md +0 -137
- package/framework/agents/infrastructure/devops-engineer.md +0 -126
- package/framework/agents/integrations/azure-identity.md +0 -148
- package/framework/agents/quality/code-analyzer.md +0 -239
- package/framework/agents/quality/testing-specialist.md +0 -130
- package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
- package/framework/index/troubleshooting-index.json +0 -184
- package/framework/rules/testing-standards.md +0 -14
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
- package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
- package/framework/standards/ai-agents/blazor-ui.md +0 -364
- package/framework/standards/architecture/ddd/aggregates.md +0 -120
- package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
- package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
- package/framework/standards/architecture/ddd/entities.md +0 -99
- package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
- package/framework/standards/architecture/ddd/value-objects.md +0 -124
- package/framework/standards/backend/api/minimal-api.md +0 -494
- package/framework/standards/backend/api/rest.md +0 -492
- package/framework/standards/backend/authentication/passkeys.md +0 -428
- package/framework/standards/backend/database/ef-core.md +0 -199
- package/framework/standards/backend/database/migrations.md +0 -393
- package/framework/standards/backend/database/repository-patterns.md +0 -528
- package/framework/standards/backend/dotnet/async.md +0 -366
- package/framework/standards/backend/dotnet/core.md +0 -117
- package/framework/standards/backend/dotnet/di.md +0 -439
- package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
- package/framework/standards/context/analytics.md +0 -96
- package/framework/standards/context/bundles.md +0 -110
- package/framework/standards/context/priming.md +0 -78
- package/framework/standards/core/architecture.md +0 -185
- package/framework/standards/core/coding.md +0 -214
- package/framework/standards/core/git-branching-strategy.md +0 -403
- package/framework/standards/core/git.md +0 -185
- package/framework/standards/core/testing.md +0 -295
- package/framework/standards/data/nosql/blob-storage.md +0 -102
- package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
- package/framework/standards/frontend/blazor/design-checklist.md +0 -222
- package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
- package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
- package/framework/standards/frontend/blazor/html-conversion.md +0 -184
- package/framework/standards/frontend/blazor/lifecycle.md +0 -195
- package/framework/standards/frontend/blazor/pitfalls.md +0 -198
- package/framework/standards/frontend/blazor/state.md +0 -191
- package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -266
- package/framework/standards/frontend/design-system/design-trends.md +0 -171
- package/framework/standards/infrastructure/azure/azure.md +0 -624
- package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
- package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
- package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
- package/framework/standards/infrastructure/azure/services/functions.md +0 -486
- package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
- package/framework/standards/infrastructure/azure/services/storage.md +0 -407
- package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
- package/framework/standards/integration/api/graphql.md +0 -91
- package/framework/standards/integration/api/grpc.md +0 -114
- package/framework/standards/integration/api/rest-design.md +0 -95
- package/framework/standards/integration/event-driven/cqrs.md +0 -101
- package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
- package/framework/standards/integration/event-driven/service-bus.md +0 -95
- package/framework/standards/observability/logging.md +0 -131
- package/framework/standards/observability/metrics.md +0 -121
- package/framework/standards/observability/monitoring.md +0 -114
- package/framework/standards/observability/tracing.md +0 -132
- package/framework/standards/workflows/parallel-execution.md +0 -112
- package/framework/standards/workflows/thread-management.md +0 -113
- package/framework/templates/code/dotnet/backend/service.cs +0 -139
- package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
- package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
- package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
- package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
- package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
- package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
- package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
- package/framework/templates/code/dotnet/frontend/component.razor +0 -239
- package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
- package/framework/templates/infrastructure/azure/README.md +0 -286
- package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
- package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
- package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
- package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
- package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
- package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
- package/framework/templates/infrastructure/azure/deploy.sh +0 -208
- package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
- package/framework/templates/infrastructure/azure/main.bicep +0 -189
- package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
- package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
- package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
- package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
- package/framework/templates/infrastructure/azure/storage.bicep +0 -106
- package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
- package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
- package/framework/templates/integrations/azure-identity-config.cs +0 -288
- package/framework/templates/project-structure/dotnet-ddd.md +0 -70
- package/framework/templates/saas/subscription.cs +0 -347
- package/framework/templates/saas/tenant.cs +0 -338
- package/framework/templates/ui/FluentDesignTheme.cs +0 -149
- package/framework/templates/ui/MudTheme.cs +0 -281
- package/framework/workflows/configs/nodejs-cli.json +0 -40
- package/src/commands/standards/context-budget.js +0 -89
- package/src/lib/standards/index.js +0 -7
- package/src/lib/standards/standards-context-injector.js +0 -302
- package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
- package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
- package/src/lib/validators/blazor/blazor-validator.js +0 -369
- package/src/lib/validators/blazor/index.js +0 -9
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared issue counting utility for validators
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
warnings: issues.filter((i) => i.type === 'warning').length,
|
|
16
|
-
infos: issues.filter((i) => i.type === 'info').length,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared issue counting utility for validators
|
|
3
|
+
*
|
|
4
|
+
*
|
|
5
|
+
* @param {Array} issues - Array of validation issues with type property
|
|
6
|
+
* @returns {{errors: number, warnings: number, infos: number}} Count by type
|
|
7
|
+
*/
|
|
8
|
+
export function countIssues(issues) {
|
|
9
|
+
return {
|
|
10
|
+
errors: issues.filter((i) => i.type === 'error').length,
|
|
11
|
+
warnings: issues.filter((i) => i.type === 'warning').length,
|
|
12
|
+
infos: issues.filter((i) => i.type === 'info').length,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -14,10 +14,9 @@ import { loadState } from '../../core/state/state-manager.js';
|
|
|
14
14
|
import { getAbsoluteOutputPath, resolveContractsOutputType } from '../../core/paths/output-schema.js';
|
|
15
15
|
import { getStackProfile } from '../stack/stack-profile.js';
|
|
16
16
|
|
|
17
|
-
/** Validators that only make sense for .NET
|
|
17
|
+
/** Validators that only make sense for .NET projects. */
|
|
18
18
|
const DOTNET_ONLY_VALIDATORS = new Set([
|
|
19
|
-
'architecture', 'packages', '
|
|
20
|
-
'blazor-state', 'contract-compliance'
|
|
19
|
+
'architecture', 'packages', 'contract-compliance'
|
|
21
20
|
]);
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -26,7 +25,7 @@ const DOTNET_ONLY_VALIDATORS = new Set([
|
|
|
26
25
|
*/
|
|
27
26
|
const HEAVY_VALIDATORS = new Set([
|
|
28
27
|
'architecture', 'packages', 'contract-compliance',
|
|
29
|
-
'
|
|
28
|
+
'css', 'design-system'
|
|
30
29
|
]);
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -228,21 +227,6 @@ async function runSingleValidator(validatorId, projectPath, featureName, options
|
|
|
228
227
|
return await validateContrast(projectPath, options);
|
|
229
228
|
}
|
|
230
229
|
|
|
231
|
-
case 'blazor': {
|
|
232
|
-
const { validateBlazorPatterns } = await import('./blazor/blazor-validator.js');
|
|
233
|
-
return await validateBlazorPatterns(projectPath, options);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
case 'blazor-concurrency': {
|
|
237
|
-
const { validateConcurrencyFiles } = await import('./blazor/blazor-concurrency-analyzer.js');
|
|
238
|
-
return await validateConcurrencyFiles(projectPath, options);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
case 'blazor-state': {
|
|
242
|
-
const { validateState } = await import('./blazor/blazor-state-validator.js');
|
|
243
|
-
return await validateState(projectPath, options);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
230
|
case 'css': {
|
|
247
231
|
const { validateCss } = await import('./css/css-validator.js');
|
|
248
232
|
return await validateCss(projectPath, options);
|
|
@@ -141,7 +141,7 @@ export async function setupInfra(targetPath, { _exec = execSync } = {}) {
|
|
|
141
141
|
const contextReadmePath = join(contextDir, 'README.md');
|
|
142
142
|
if (!(await pathExists(contextReadmePath))) {
|
|
143
143
|
const dirName = targetPath.split(/[\/]/).pop();
|
|
144
|
-
const readmeContent = `# ${dirName} — Project Context\n\nRun
|
|
144
|
+
const readmeContent = `# ${dirName} — Project Context\n\nRun \`morph-spec context refresh\` to generate architecture context.\n`;
|
|
145
145
|
await writeFile(contextReadmePath, readmeContent);
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -103,23 +103,31 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
103
103
|
/**
|
|
104
104
|
* Tier-based defaults for Claude Code subagent frontmatter fields.
|
|
105
105
|
*
|
|
106
|
-
* Tier 1 (Orchestrators): can spawn subagents via
|
|
107
|
-
* Tier 2 (Domain Leaders): coordinate specialists
|
|
106
|
+
* Tier 1 (Orchestrators): opus model, can spawn subagents via Agent tool, 30 turns, project memory, high effort.
|
|
107
|
+
* Tier 2 (Domain Leaders): sonnet model, coordinate specialists, 20 turns, local memory, medium effort.
|
|
108
|
+
* Tier 3 (Specialists): sonnet model, implementation focus, 15 turns, local memory, medium effort.
|
|
109
|
+
* Tier 4 (Validators): haiku model, read-only scanning, 10 turns, background execution, low effort.
|
|
108
110
|
*/
|
|
109
111
|
const TIER_DEFAULTS = {
|
|
110
112
|
1: {
|
|
111
|
-
model: '
|
|
112
|
-
tools: 'Read, Grep, Glob, Bash,
|
|
113
|
+
model: 'opus',
|
|
114
|
+
tools: 'Read, Grep, Glob, Bash, Agent, Write, Edit',
|
|
113
115
|
maxTurns: 30,
|
|
114
116
|
skills: ['morph-checklist'],
|
|
115
117
|
memory: 'project',
|
|
118
|
+
effort: 'high',
|
|
119
|
+
permissionMode: 'acceptEdits',
|
|
120
|
+
background: false,
|
|
116
121
|
},
|
|
117
122
|
2: {
|
|
118
|
-
model: '
|
|
123
|
+
model: 'sonnet',
|
|
119
124
|
tools: 'Read, Write, Edit, Bash, Glob, Grep',
|
|
120
125
|
maxTurns: 20,
|
|
121
126
|
skills: ['morph-checklist'],
|
|
122
127
|
memory: 'local',
|
|
128
|
+
effort: 'medium',
|
|
129
|
+
permissionMode: 'acceptEdits',
|
|
130
|
+
background: false,
|
|
123
131
|
},
|
|
124
132
|
3: {
|
|
125
133
|
model: 'sonnet',
|
|
@@ -127,6 +135,9 @@ const TIER_DEFAULTS = {
|
|
|
127
135
|
maxTurns: 15,
|
|
128
136
|
skills: [],
|
|
129
137
|
memory: 'local',
|
|
138
|
+
effort: 'medium',
|
|
139
|
+
permissionMode: 'default',
|
|
140
|
+
background: false,
|
|
130
141
|
},
|
|
131
142
|
4: {
|
|
132
143
|
model: 'haiku',
|
|
@@ -134,6 +145,9 @@ const TIER_DEFAULTS = {
|
|
|
134
145
|
maxTurns: 10,
|
|
135
146
|
skills: [],
|
|
136
147
|
memory: 'project',
|
|
148
|
+
effort: 'low',
|
|
149
|
+
permissionMode: 'plan',
|
|
150
|
+
background: true,
|
|
137
151
|
},
|
|
138
152
|
};
|
|
139
153
|
|
|
@@ -149,10 +163,14 @@ function buildFrontmatter(agent, description) {
|
|
|
149
163
|
const defaults = TIER_DEFAULTS[agent.tier] ?? TIER_DEFAULTS[2];
|
|
150
164
|
const name = agent.title ?? agent.name;
|
|
151
165
|
|
|
152
|
-
// Allow per-agent
|
|
166
|
+
// Allow per-agent overrides from teammate config
|
|
153
167
|
const tools = agent.teammate?.tools ?? defaults.tools;
|
|
154
|
-
// Allow per-agent maxTurns override
|
|
155
168
|
const maxTurns = agent.teammate?.maxTurns ?? defaults.maxTurns;
|
|
169
|
+
const effort = agent.teammate?.effort ?? defaults.effort;
|
|
170
|
+
const permissionMode = agent.teammate?.permissionMode ?? defaults.permissionMode;
|
|
171
|
+
const background = agent.teammate?.background ?? defaults.background;
|
|
172
|
+
// color is a top-level field on the agent (not inside teammate)
|
|
173
|
+
const color = agent.color;
|
|
156
174
|
|
|
157
175
|
const lines = [
|
|
158
176
|
`name: ${name}`,
|
|
@@ -162,6 +180,11 @@ function buildFrontmatter(agent, description) {
|
|
|
162
180
|
`maxTurns: ${maxTurns}`,
|
|
163
181
|
];
|
|
164
182
|
|
|
183
|
+
if (color) lines.push(`color: ${color}`);
|
|
184
|
+
if (effort) lines.push(`effort: ${effort}`);
|
|
185
|
+
if (permissionMode && permissionMode !== 'default') lines.push(`permissionMode: ${permissionMode}`);
|
|
186
|
+
if (background) lines.push(`background: true`);
|
|
187
|
+
|
|
165
188
|
if (defaults.skills.length > 0) {
|
|
166
189
|
const skillsList = defaults.skills.map(s => ` - ${s}`).join('\n');
|
|
167
190
|
lines.push(`skills:`, skillsList);
|
|
@@ -171,10 +194,30 @@ function buildFrontmatter(agent, description) {
|
|
|
171
194
|
return lines.join('\n');
|
|
172
195
|
}
|
|
173
196
|
|
|
197
|
+
/** Maps agent group to task categories for routing hints in description */
|
|
198
|
+
const GROUP_TO_CATEGORIES = {
|
|
199
|
+
'backend': 'Domain, Infrastructure, Application',
|
|
200
|
+
'frontend': 'Presentation',
|
|
201
|
+
'domain-analysis': 'Architecture, Cross-cutting',
|
|
202
|
+
'testing': 'Tests',
|
|
203
|
+
'infra': 'Infra, DevOps',
|
|
204
|
+
'ai': 'AI, Automation',
|
|
205
|
+
};
|
|
206
|
+
|
|
174
207
|
function buildDescription(agent) {
|
|
175
|
-
const
|
|
208
|
+
const role = agent.teammate?.role ?? `Handles ${(agent.domains ?? []).join(', ')}`;
|
|
209
|
+
const triggers = agent.teammate?.triggers;
|
|
210
|
+
const categories = GROUP_TO_CATEGORIES[agent.group];
|
|
211
|
+
|
|
212
|
+
let desc = role;
|
|
213
|
+
if (triggers) {
|
|
214
|
+
desc += `. Use proactively when: ${triggers}`;
|
|
215
|
+
} else if (categories) {
|
|
216
|
+
desc += ` | Tasks: ${categories}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
176
219
|
// Truncate to 200 chars for settings display
|
|
177
|
-
return
|
|
220
|
+
return desc.length > 200 ? desc.slice(0, 197) + '...' : desc;
|
|
178
221
|
}
|
|
179
222
|
|
|
180
223
|
/**
|
|
@@ -200,8 +243,16 @@ function buildBody(agent) {
|
|
|
200
243
|
lines.push(`\n## Domains\n${agent.domains.map(d => `- ${d}`).join('\n')}`);
|
|
201
244
|
}
|
|
202
245
|
}
|
|
246
|
+
|
|
247
|
+
// Task routing — tells the agent which task categories it handles
|
|
248
|
+
const categories = GROUP_TO_CATEGORIES[agent.group];
|
|
249
|
+
if (categories) {
|
|
250
|
+
lines.push(`\n## Task Routing\nHandle tasks categorized as: ${categories}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Key standards — @ references for upfront awareness
|
|
203
254
|
if (agent.standards?.length) {
|
|
204
|
-
lines.push(`\n## Standards
|
|
255
|
+
lines.push(`\n## Key Standards\n${agent.standards.map(formatStandardRef).join('\n')}`);
|
|
205
256
|
}
|
|
206
257
|
return lines.join('\n');
|
|
207
258
|
}
|
package/src/utils/file-copier.js
CHANGED
|
@@ -15,7 +15,7 @@ import { homedir } from 'os';
|
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
16
|
|
|
17
17
|
/** Current hooks schema version — bump when hook definitions change */
|
|
18
|
-
const HOOKS_VERSION = '2.
|
|
18
|
+
const HOOKS_VERSION = '2.14.0';
|
|
19
19
|
|
|
20
20
|
/** Marker for old dispatch.js (v1) */
|
|
21
21
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -70,6 +70,16 @@ const MORPH_HOOKS = [
|
|
|
70
70
|
}]
|
|
71
71
|
},
|
|
72
72
|
|
|
73
|
+
// === SessionStart: resume only — orientation context ===
|
|
74
|
+
{
|
|
75
|
+
event: 'SessionStart',
|
|
76
|
+
matcher: 'resume',
|
|
77
|
+
hooks: [{
|
|
78
|
+
type: 'command',
|
|
79
|
+
command: 'node framework/hooks/claude-code/session-start/resume-context.js'
|
|
80
|
+
}]
|
|
81
|
+
},
|
|
82
|
+
|
|
73
83
|
// === UserPromptSubmit ===
|
|
74
84
|
{
|
|
75
85
|
event: 'UserPromptSubmit',
|
|
@@ -138,14 +148,6 @@ Otherwise respond: {"ok": true}`
|
|
|
138
148
|
type: 'command',
|
|
139
149
|
command: 'node framework/hooks/claude-code/post-tool-use/dispatch.js'
|
|
140
150
|
},
|
|
141
|
-
{
|
|
142
|
-
type: 'command',
|
|
143
|
-
command: 'node framework/hooks/claude-code/post-tool-use/context-refresh.js'
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
type: 'command',
|
|
147
|
-
command: 'node framework/hooks/claude-code/post-tool-use/skill-reminder.js'
|
|
148
|
-
},
|
|
149
151
|
{
|
|
150
152
|
type: 'command',
|
|
151
153
|
command: 'node framework/hooks/claude-code/post-tool-use/validator-feedback.js'
|
|
@@ -184,16 +186,6 @@ Otherwise respond: {"ok": true}`
|
|
|
184
186
|
}]
|
|
185
187
|
},
|
|
186
188
|
|
|
187
|
-
// === Notification ===
|
|
188
|
-
{
|
|
189
|
-
event: 'Notification',
|
|
190
|
-
matcher: 'idle_prompt',
|
|
191
|
-
hooks: [{
|
|
192
|
-
type: 'command',
|
|
193
|
-
command: 'node framework/hooks/claude-code/notification/approval-reminder.js'
|
|
194
|
-
}]
|
|
195
|
-
},
|
|
196
|
-
|
|
197
189
|
// === TeammateIdle ===
|
|
198
190
|
// Fires when an Agent Team teammate finishes and is about to go idle.
|
|
199
191
|
// Exit 2 = send feedback to teammate to continue working (validation failed).
|
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skills Installer
|
|
3
|
-
*
|
|
4
|
-
* Copies framework skills to .claude/skills/ during morph-spec init.
|
|
5
|
-
* Installs level-0-meta and level-1-workflows skills using the native Claude Code
|
|
6
|
-
* subdirectory format: .claude/skills/{skill-name}/SKILL.md (plus optional
|
|
7
|
-
* scripts/, references/, and assets/ subdirectories).
|
|
8
|
-
* Level-2 domain specialists are installed as native subagents via installDomainAgents().
|
|
9
|
-
* Level-3 and level-4 only contain README.md placeholder files and are skipped.
|
|
10
|
-
*
|
|
11
|
-
* Source skills can be either:
|
|
12
|
-
* - Directory-based: framework/skills/{level}/{skill-name}/SKILL.md (preferred)
|
|
13
|
-
* - Flat file: framework/skills/{level}/{skill-name}.md (legacy, still supported)
|
|
14
|
-
*
|
|
15
|
-
* Directory-based skills have their entire directory copied (scripts/, references/,
|
|
16
|
-
* assets/) so Claude Code can execute scripts and load references on demand.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
|
|
20
|
-
import { join } from 'path';
|
|
21
|
-
import { fileURLToPath } from 'url';
|
|
22
|
-
import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
23
|
-
|
|
24
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
25
|
-
const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Skill levels to install in .claude/skills/.
|
|
29
|
-
* Level-0 meta skills (planning, reviewing) and level-1 workflow skills
|
|
30
|
-
* (phase-design, phase-implement, etc.) are installed. Level-2 domain
|
|
31
|
-
* specialists are installed as native subagents via installDomainAgents()
|
|
32
|
-
* instead. Level-3 and level-4 are skipped (no content yet).
|
|
33
|
-
*/
|
|
34
|
-
const SKILL_LEVELS_TO_INSTALL = ['level-0-meta', 'level-1-workflows'];
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Check if a skill should be installed based on its stacks: frontmatter.
|
|
38
|
-
* @param {string} skillContent - Raw SKILL.md content
|
|
39
|
-
* @param {string[]} projectTags - Stack tags from config
|
|
40
|
-
* @returns {boolean}
|
|
41
|
-
*/
|
|
42
|
-
export function shouldInstallSkill(skillContent, projectTags) {
|
|
43
|
-
return shouldInstall(parseStacks(skillContent), projectTags);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Recursively copy a directory tree from src to dest.
|
|
48
|
-
* Creates dest if it doesn't exist. Skips README.md files.
|
|
49
|
-
*
|
|
50
|
-
* @param {string} src - Source directory path
|
|
51
|
-
* @param {string} dest - Destination directory path
|
|
52
|
-
*/
|
|
53
|
-
function copyDirectory(src, dest) {
|
|
54
|
-
mkdirSync(dest, { recursive: true });
|
|
55
|
-
for (const entry of readdirSync(src)) {
|
|
56
|
-
if (entry === 'README.md') continue;
|
|
57
|
-
const srcPath = join(src, entry);
|
|
58
|
-
const destPath = join(dest, entry);
|
|
59
|
-
if (statSync(srcPath).isDirectory()) {
|
|
60
|
-
copyDirectory(srcPath, destPath);
|
|
61
|
-
} else {
|
|
62
|
-
copyFileSync(srcPath, destPath);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Walk srcDir and install skills into destDir.
|
|
69
|
-
*
|
|
70
|
-
* Handles two source formats:
|
|
71
|
-
* 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
|
|
72
|
-
* → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
|
|
73
|
-
* → destDir/{skill-name}/
|
|
74
|
-
*
|
|
75
|
-
* 2. Flat .md skill (legacy): srcDir/{skill-name}.md
|
|
76
|
-
* → destDir/{skill-name}/SKILL.md
|
|
77
|
-
*
|
|
78
|
-
* Level directories (level-0-meta, level-1-workflows, sub-categories) are
|
|
79
|
-
* traversed recursively. A directory is recognized as a skill directory when
|
|
80
|
-
* it contains a SKILL.md file.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} srcDir - Source directory to walk
|
|
83
|
-
* @param {string} destDir - Destination base directory (.claude/skills/)
|
|
84
|
-
*/
|
|
85
|
-
function installSkillsFromDir(srcDir, destDir, projectTags) {
|
|
86
|
-
const entries = readdirSync(srcDir);
|
|
87
|
-
for (const entry of entries) {
|
|
88
|
-
const srcPath = join(srcDir, entry);
|
|
89
|
-
const stat = statSync(srcPath);
|
|
90
|
-
|
|
91
|
-
if (stat.isDirectory()) {
|
|
92
|
-
const skillMdPath = join(srcPath, 'SKILL.md');
|
|
93
|
-
if (existsSync(skillMdPath)) {
|
|
94
|
-
// Directory-based skill — check stack filter before copying
|
|
95
|
-
const content = readFileSync(skillMdPath, 'utf-8');
|
|
96
|
-
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
97
|
-
const skillDestDir = join(destDir, entry);
|
|
98
|
-
copyDirectory(srcPath, skillDestDir);
|
|
99
|
-
} else {
|
|
100
|
-
// Level/category directory — recurse
|
|
101
|
-
installSkillsFromDir(srcPath, destDir, projectTags);
|
|
102
|
-
}
|
|
103
|
-
} else if (entry.endsWith('.md') && entry !== 'README.md') {
|
|
104
|
-
// Legacy flat .md skill — check stack filter before copying
|
|
105
|
-
const content = readFileSync(srcPath, 'utf-8');
|
|
106
|
-
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
107
|
-
const skillName = entry.slice(0, -3);
|
|
108
|
-
const skillDir = join(destDir, skillName);
|
|
109
|
-
mkdirSync(skillDir, { recursive: true });
|
|
110
|
-
copyFileSync(srcPath, join(skillDir, 'SKILL.md'));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function collectSourceSkillNames() {
|
|
116
|
-
const names = new Set();
|
|
117
|
-
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
118
|
-
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
119
|
-
if (!existsSync(levelDir)) continue;
|
|
120
|
-
collectNamesFromDir(levelDir, names);
|
|
121
|
-
}
|
|
122
|
-
return names;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function collectNamesFromDir(dir, names) {
|
|
126
|
-
for (const entry of readdirSync(dir)) {
|
|
127
|
-
const fullPath = join(dir, entry);
|
|
128
|
-
if (!statSync(fullPath).isDirectory()) continue;
|
|
129
|
-
if (existsSync(join(fullPath, 'SKILL.md'))) {
|
|
130
|
-
names.add(entry);
|
|
131
|
-
} else {
|
|
132
|
-
collectNamesFromDir(fullPath, names);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Install morph framework skills to .claude/skills/ in the target project.
|
|
139
|
-
* Skills are copied into subdirectories as SKILL.md files so Claude Code can
|
|
140
|
-
* discover them natively via /<skill-name> syntax (e.g., /phase-design,
|
|
141
|
-
* /
|
|
142
|
-
* references/, and assets/ subdirectories copied for on-demand loading.
|
|
143
|
-
*
|
|
144
|
-
* Stack filtering: if projectTags is provided, only skills whose `stacks:`
|
|
145
|
-
* frontmatter matches the project tags are installed. Skills without a
|
|
146
|
-
* `stacks:` field are always installed (universal).
|
|
147
|
-
*
|
|
148
|
-
* @param {string} projectDir - Target project root directory
|
|
149
|
-
* @param {Object} [options]
|
|
150
|
-
* @param {string[]} [options.projectTags=[]] - Stack tags from config
|
|
151
|
-
*/
|
|
152
|
-
export async function installSkills(projectDir, { projectTags = [] } = {}) {
|
|
153
|
-
const claudeSkillsDir = join(projectDir, '.claude', 'skills');
|
|
154
|
-
mkdirSync(claudeSkillsDir, { recursive: true });
|
|
155
|
-
|
|
156
|
-
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
157
|
-
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
158
|
-
if (!existsSync(levelDir)) continue;
|
|
159
|
-
installSkillsFromDir(levelDir, claudeSkillsDir, projectTags);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Collect installed morph-* skill names
|
|
163
|
-
const installed = new Set();
|
|
164
|
-
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
165
|
-
if (existsSync(join(claudeSkillsDir, entry, 'SKILL.md'))) {
|
|
166
|
-
installed.add(entry);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Cleanup orphan morph-* skill dirs that exist in source but were filtered out
|
|
171
|
-
const allSourceSkills = collectSourceSkillNames();
|
|
172
|
-
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
173
|
-
if (!entry.startsWith('morph-')) continue;
|
|
174
|
-
if (!installed.has(entry) && allSourceSkills.has(entry)) {
|
|
175
|
-
rmSync(join(claudeSkillsDir, entry), { recursive: true, force: true });
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Skills Installer
|
|
3
|
+
*
|
|
4
|
+
* Copies framework skills to .claude/skills/ during morph-spec init.
|
|
5
|
+
* Installs level-0-meta and level-1-workflows skills using the native Claude Code
|
|
6
|
+
* subdirectory format: .claude/skills/{skill-name}/SKILL.md (plus optional
|
|
7
|
+
* scripts/, references/, and assets/ subdirectories).
|
|
8
|
+
* Level-2 domain specialists are installed as native subagents via installDomainAgents().
|
|
9
|
+
* Level-3 and level-4 only contain README.md placeholder files and are skipped.
|
|
10
|
+
*
|
|
11
|
+
* Source skills can be either:
|
|
12
|
+
* - Directory-based: framework/skills/{level}/{skill-name}/SKILL.md (preferred)
|
|
13
|
+
* - Flat file: framework/skills/{level}/{skill-name}.md (legacy, still supported)
|
|
14
|
+
*
|
|
15
|
+
* Directory-based skills have their entire directory copied (scripts/, references/,
|
|
16
|
+
* assets/) so Claude Code can execute scripts and load references on demand.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
23
|
+
|
|
24
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
25
|
+
const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Skill levels to install in .claude/skills/.
|
|
29
|
+
* Level-0 meta skills (planning, reviewing) and level-1 workflow skills
|
|
30
|
+
* (phase-design, phase-implement, etc.) are installed. Level-2 domain
|
|
31
|
+
* specialists are installed as native subagents via installDomainAgents()
|
|
32
|
+
* instead. Level-3 and level-4 are skipped (no content yet).
|
|
33
|
+
*/
|
|
34
|
+
const SKILL_LEVELS_TO_INSTALL = ['level-0-meta', 'level-1-workflows'];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a skill should be installed based on its stacks: frontmatter.
|
|
38
|
+
* @param {string} skillContent - Raw SKILL.md content
|
|
39
|
+
* @param {string[]} projectTags - Stack tags from config
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function shouldInstallSkill(skillContent, projectTags) {
|
|
43
|
+
return shouldInstall(parseStacks(skillContent), projectTags);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Recursively copy a directory tree from src to dest.
|
|
48
|
+
* Creates dest if it doesn't exist. Skips README.md files.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} src - Source directory path
|
|
51
|
+
* @param {string} dest - Destination directory path
|
|
52
|
+
*/
|
|
53
|
+
function copyDirectory(src, dest) {
|
|
54
|
+
mkdirSync(dest, { recursive: true });
|
|
55
|
+
for (const entry of readdirSync(src)) {
|
|
56
|
+
if (entry === 'README.md') continue;
|
|
57
|
+
const srcPath = join(src, entry);
|
|
58
|
+
const destPath = join(dest, entry);
|
|
59
|
+
if (statSync(srcPath).isDirectory()) {
|
|
60
|
+
copyDirectory(srcPath, destPath);
|
|
61
|
+
} else {
|
|
62
|
+
copyFileSync(srcPath, destPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Walk srcDir and install skills into destDir.
|
|
69
|
+
*
|
|
70
|
+
* Handles two source formats:
|
|
71
|
+
* 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
|
|
72
|
+
* → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
|
|
73
|
+
* → destDir/{skill-name}/
|
|
74
|
+
*
|
|
75
|
+
* 2. Flat .md skill (legacy): srcDir/{skill-name}.md
|
|
76
|
+
* → destDir/{skill-name}/SKILL.md
|
|
77
|
+
*
|
|
78
|
+
* Level directories (level-0-meta, level-1-workflows, sub-categories) are
|
|
79
|
+
* traversed recursively. A directory is recognized as a skill directory when
|
|
80
|
+
* it contains a SKILL.md file.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} srcDir - Source directory to walk
|
|
83
|
+
* @param {string} destDir - Destination base directory (.claude/skills/)
|
|
84
|
+
*/
|
|
85
|
+
function installSkillsFromDir(srcDir, destDir, projectTags) {
|
|
86
|
+
const entries = readdirSync(srcDir);
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const srcPath = join(srcDir, entry);
|
|
89
|
+
const stat = statSync(srcPath);
|
|
90
|
+
|
|
91
|
+
if (stat.isDirectory()) {
|
|
92
|
+
const skillMdPath = join(srcPath, 'SKILL.md');
|
|
93
|
+
if (existsSync(skillMdPath)) {
|
|
94
|
+
// Directory-based skill — check stack filter before copying
|
|
95
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
96
|
+
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
97
|
+
const skillDestDir = join(destDir, entry);
|
|
98
|
+
copyDirectory(srcPath, skillDestDir);
|
|
99
|
+
} else {
|
|
100
|
+
// Level/category directory — recurse
|
|
101
|
+
installSkillsFromDir(srcPath, destDir, projectTags);
|
|
102
|
+
}
|
|
103
|
+
} else if (entry.endsWith('.md') && entry !== 'README.md') {
|
|
104
|
+
// Legacy flat .md skill — check stack filter before copying
|
|
105
|
+
const content = readFileSync(srcPath, 'utf-8');
|
|
106
|
+
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
107
|
+
const skillName = entry.slice(0, -3);
|
|
108
|
+
const skillDir = join(destDir, skillName);
|
|
109
|
+
mkdirSync(skillDir, { recursive: true });
|
|
110
|
+
copyFileSync(srcPath, join(skillDir, 'SKILL.md'));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function collectSourceSkillNames() {
|
|
116
|
+
const names = new Set();
|
|
117
|
+
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
118
|
+
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
119
|
+
if (!existsSync(levelDir)) continue;
|
|
120
|
+
collectNamesFromDir(levelDir, names);
|
|
121
|
+
}
|
|
122
|
+
return names;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function collectNamesFromDir(dir, names) {
|
|
126
|
+
for (const entry of readdirSync(dir)) {
|
|
127
|
+
const fullPath = join(dir, entry);
|
|
128
|
+
if (!statSync(fullPath).isDirectory()) continue;
|
|
129
|
+
if (existsSync(join(fullPath, 'SKILL.md'))) {
|
|
130
|
+
names.add(entry);
|
|
131
|
+
} else {
|
|
132
|
+
collectNamesFromDir(fullPath, names);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Install morph framework skills to .claude/skills/ in the target project.
|
|
139
|
+
* Skills are copied into subdirectories as SKILL.md files so Claude Code can
|
|
140
|
+
* discover them natively via /<skill-name> syntax (e.g., /phase-design,
|
|
141
|
+
* /nextjs-expert). Directory-based skills also get their scripts/,
|
|
142
|
+
* references/, and assets/ subdirectories copied for on-demand loading.
|
|
143
|
+
*
|
|
144
|
+
* Stack filtering: if projectTags is provided, only skills whose `stacks:`
|
|
145
|
+
* frontmatter matches the project tags are installed. Skills without a
|
|
146
|
+
* `stacks:` field are always installed (universal).
|
|
147
|
+
*
|
|
148
|
+
* @param {string} projectDir - Target project root directory
|
|
149
|
+
* @param {Object} [options]
|
|
150
|
+
* @param {string[]} [options.projectTags=[]] - Stack tags from config
|
|
151
|
+
*/
|
|
152
|
+
export async function installSkills(projectDir, { projectTags = [] } = {}) {
|
|
153
|
+
const claudeSkillsDir = join(projectDir, '.claude', 'skills');
|
|
154
|
+
mkdirSync(claudeSkillsDir, { recursive: true });
|
|
155
|
+
|
|
156
|
+
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
157
|
+
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
158
|
+
if (!existsSync(levelDir)) continue;
|
|
159
|
+
installSkillsFromDir(levelDir, claudeSkillsDir, projectTags);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Collect installed morph-* skill names
|
|
163
|
+
const installed = new Set();
|
|
164
|
+
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
165
|
+
if (existsSync(join(claudeSkillsDir, entry, 'SKILL.md'))) {
|
|
166
|
+
installed.add(entry);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Cleanup orphan morph-* skill dirs that exist in source but were filtered out
|
|
171
|
+
const allSourceSkills = collectSourceSkillNames();
|
|
172
|
+
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
173
|
+
if (!entry.startsWith('morph-')) continue;
|
|
174
|
+
if (!installed.has(entry) && allSourceSkills.has(entry)) {
|
|
175
|
+
rmSync(join(claudeSkillsDir, entry), { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|