@polymorphism-tech/morph-spec 8.33.0 → 8.35.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 +22 -2
- package/README.md +2 -2
- package/bin/morph-spec.js +43 -5
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +20 -22
- package/framework/agents.json +198 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +238 -10
- package/framework/evals/proof-rubrics/README.md +76 -0
- package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
- package/framework/evals/proof-rubrics/fiacao.md +54 -0
- package/framework/evals/proof-rubrics/guard-cego.md +52 -0
- package/framework/evals/proof-rubrics/media-claims.md +109 -0
- package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
- package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
- package/framework/evals/rubrics/architecture.md +30 -0
- package/framework/evals/rubrics/code-quality.md +26 -0
- package/framework/evals/rubrics/test-coverage.md +20 -0
- package/framework/hooks/README.md +11 -1
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
- package/framework/hooks/claude-code/core/chain.js +27 -7
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-context.js +83 -14
- package/framework/hooks/claude-code/core/worktree-create.js +206 -0
- package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
- package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
- package/framework/hooks/shared/command-tokens.js +23 -0
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/git-paths.js +25 -0
- package/framework/hooks/shared/hook-response.js +17 -0
- package/framework/hooks/shared/morph-cli.js +134 -0
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/rules/infrastructure-standards.md +3 -1
- package/framework/schemas/ai-pin.schema.json +97 -0
- package/framework/schemas/config.schema.json +107 -0
- package/framework/schemas/feature.schema.json +23 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +28 -7
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +20 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +38 -4
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +166 -9
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/PROVENANCE.json +81 -3
- package/framework/standards/STANDARDS.json +1504 -830
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +256 -160
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +217 -193
- package/framework/standards/ai-agents/context-providers.md +261 -188
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
- package/framework/standards/ai-agents/cost-and-budget.md +256 -0
- package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
- package/framework/standards/ai-agents/evals-with-cache.md +289 -0
- package/framework/standards/ai-agents/guardrails.md +222 -0
- package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
- package/framework/standards/ai-agents/mcp-server.md +351 -250
- package/framework/standards/ai-agents/mcp-tools.md +250 -197
- package/framework/standards/ai-agents/media-pipeline.md +352 -0
- package/framework/standards/ai-agents/media-video.md +267 -0
- package/framework/standards/ai-agents/middleware-patterns.md +241 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
- package/framework/standards/ai-agents/modalities-vision.md +205 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +260 -231
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +293 -158
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +143 -135
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +280 -249
- package/framework/standards/ai-agents/sweet-spot.md +129 -109
- package/framework/standards/ai-agents/testing-ai.md +236 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
- package/framework/standards/ai-agents/workflows.md +27 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +90 -0
- package/framework/templates/REGISTRY.json +14 -1
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
- package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
- package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
- package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
- package/framework/templates/dotnet/ai-kit/README.md +128 -0
- package/framework/templates/dotnet/ai-kit/global.json +9 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +24 -14
- package/package.json +3 -3
- package/src/commands/e2e/e2e.js +91 -40
- package/src/commands/project/doctor.js +143 -6
- package/src/commands/project/env.js +95 -0
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +274 -53
- package/src/commands/state/advance.js +24 -2
- package/src/commands/state/approve.js +33 -1
- package/src/commands/state/create.js +72 -11
- package/src/commands/state/delete.js +8 -0
- package/src/commands/state/finish.js +360 -53
- package/src/commands/state/gate-check.js +190 -6
- package/src/commands/state/score.js +36 -1
- package/src/commands/validation/verify.js +134 -25
- package/src/core/state/state-manager.js +9 -1
- package/src/dashboard/app/pages/HookInspector.jsx +430 -418
- package/src/dashboard/scanner/hooks-scanner.js +5 -1
- package/src/lib/ai/ai-checks.js +182 -0
- package/src/lib/ai/ai-project-scan.js +347 -0
- package/src/lib/ai/code-audit.js +181 -0
- package/src/lib/ai/comment-stripper.js +184 -0
- package/src/lib/ai/pin-drift.js +138 -0
- package/src/lib/ai/registry-audit.js +285 -0
- package/src/lib/e2e/compose-env.js +55 -14
- package/src/lib/e2e/stack-teardown.js +115 -0
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/architecture-scanner.js +2 -1
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/graph/graphifyignore.js +2 -1
- package/src/lib/installers/effects.js +193 -0
- package/src/lib/installers/templates-reconcile.js +3 -3
- package/src/lib/risk-detector.js +284 -0
- package/src/lib/standards/verified-against.js +276 -0
- package/src/lib/tasks/eval-node.js +485 -0
- package/src/lib/tasks/linux-runner.js +297 -0
- package/src/lib/tasks/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +431 -37
- package/src/lib/validators/verify-runner.js +590 -28
- package/src/lib/worktree/dirs.js +32 -0
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +69 -11
- package/src/lib/worktree/worktree-audit.js +70 -3
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/file-copier.js +28 -16
- package/src/utils/hooks-installer.js +75 -29
- package/src/utils/skills-installer.js +194 -187
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -1,187 +1,194 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skills Installer
|
|
3
|
-
*
|
|
4
|
-
* Copies framework skills to .claude/skills/ during morph-spec init.
|
|
5
|
-
* Installs only the `core/` skill set — the canonical NLH skills referenced by
|
|
6
|
-
* MORPH.md (morph-brainstorming, morph-stack-scan, morph-plan, morph-implement,
|
|
7
|
-
* morph-eval, morph-review, morph-standards). The 3-tier system was collapsed
|
|
8
|
-
* into a single source of truth in framework/skills/core/.
|
|
9
|
-
*
|
|
10
|
-
* Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
|
|
11
|
-
* (plus optional scripts/, references/, assets/ subdirectories).
|
|
12
|
-
*
|
|
13
|
-
* Directory-based skills have their entire directory copied so Claude Code can
|
|
14
|
-
* execute scripts and load references on demand.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import { join } from 'path';
|
|
19
|
-
import { fileURLToPath } from 'url';
|
|
20
|
-
import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* @param {string
|
|
35
|
-
* @
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Skills Installer
|
|
3
|
+
*
|
|
4
|
+
* Copies framework skills to .claude/skills/ during morph-spec init.
|
|
5
|
+
* Installs only the `core/` skill set — the canonical NLH skills referenced by
|
|
6
|
+
* MORPH.md (morph-brainstorming, morph-stack-scan, morph-plan, morph-implement,
|
|
7
|
+
* morph-eval, morph-review, morph-standards). The 3-tier system was collapsed
|
|
8
|
+
* into a single source of truth in framework/skills/core/.
|
|
9
|
+
*
|
|
10
|
+
* Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
|
|
11
|
+
* (plus optional scripts/, references/, assets/ subdirectories).
|
|
12
|
+
*
|
|
13
|
+
* Directory-based skills have their entire directory copied so Claude Code can
|
|
14
|
+
* execute scripts and load references on demand.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
|
|
18
|
+
import { basename, join } from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
21
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
22
|
+
|
|
23
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
24
|
+
const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Skill levels to install in .claude/skills/.
|
|
28
|
+
* Only `core/` survives — single source of truth aligned with MORPH.md.
|
|
29
|
+
*/
|
|
30
|
+
const SKILL_LEVELS_TO_INSTALL = ['core'];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if a skill should be installed based on its stacks: frontmatter.
|
|
34
|
+
* @param {string} skillContent - Raw SKILL.md content
|
|
35
|
+
* @param {string[]} projectTags - Stack tags from config
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
export function shouldInstallSkill(skillContent, projectTags) {
|
|
39
|
+
return shouldInstall(parseStacks(skillContent), projectTags);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Copy a skill directory tree from src to dest, skipping README.md files.
|
|
44
|
+
*
|
|
45
|
+
* UM efeito por skill (não um por arquivo): é assim que o instalador pensa
|
|
46
|
+
* ("instalar a skill X"), e é o que o relatório do `--dry-run` precisa dizer.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} src - Source directory path
|
|
49
|
+
* @param {string} dest - Destination directory path
|
|
50
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} fx
|
|
51
|
+
*/
|
|
52
|
+
function copySkillDirectory(src, dest, fx) {
|
|
53
|
+
fx.copyDir(src, dest, { filter: (from) => basename(from) !== 'README.md' });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Walk srcDir and install skills into destDir.
|
|
58
|
+
*
|
|
59
|
+
* Handles two source formats:
|
|
60
|
+
* 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
|
|
61
|
+
* → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
|
|
62
|
+
* → destDir/{skill-name}/
|
|
63
|
+
*
|
|
64
|
+
* 2. Flat .md skill (legacy): srcDir/{skill-name}.md
|
|
65
|
+
* → destDir/{skill-name}/SKILL.md
|
|
66
|
+
*
|
|
67
|
+
* The `core/` level directory is traversed recursively. A directory is
|
|
68
|
+
* recognized as a skill directory when it contains a SKILL.md file.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} srcDir - Source directory to walk
|
|
71
|
+
* @param {string} destDir - Destination base directory (.claude/skills/)
|
|
72
|
+
* @param {string[]} projectTags - Stack tags from config
|
|
73
|
+
* @param {Set<string>} installedNow - Accumulator: skill names this call
|
|
74
|
+
* actually (re)installed. NOT derived by re-scanning destDir afterwards —
|
|
75
|
+
* a disk scan can't distinguish "installed this run" from "leftover from a
|
|
76
|
+
* previous run with different projectTags", which is exactly the stale
|
|
77
|
+
* entry a stack change needs cleaned up.
|
|
78
|
+
*/
|
|
79
|
+
function installSkillsFromDir(srcDir, destDir, projectTags, installedNow, fx) {
|
|
80
|
+
const entries = readdirSync(srcDir);
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const srcPath = join(srcDir, entry);
|
|
83
|
+
const stat = statSync(srcPath);
|
|
84
|
+
|
|
85
|
+
if (stat.isDirectory()) {
|
|
86
|
+
const skillMdPath = join(srcPath, 'SKILL.md');
|
|
87
|
+
if (existsSync(skillMdPath)) {
|
|
88
|
+
// Directory-based skill — check stack filter before copying.
|
|
89
|
+
// O filtro é PURO e continua sendo o único: previsão e execução
|
|
90
|
+
// percorrem esta mesma linha.
|
|
91
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
92
|
+
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
93
|
+
const skillDestDir = join(destDir, entry);
|
|
94
|
+
copySkillDirectory(srcPath, skillDestDir, fx);
|
|
95
|
+
installedNow.add(entry);
|
|
96
|
+
} else {
|
|
97
|
+
// Level/category directory — recurse
|
|
98
|
+
installSkillsFromDir(srcPath, destDir, projectTags, installedNow, fx);
|
|
99
|
+
}
|
|
100
|
+
} else if (entry.endsWith('.md') && entry !== 'README.md') {
|
|
101
|
+
// Legacy flat .md skill — check stack filter before copying
|
|
102
|
+
const content = readFileSync(srcPath, 'utf-8');
|
|
103
|
+
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
104
|
+
const skillName = entry.slice(0, -3);
|
|
105
|
+
const skillDir = join(destDir, skillName);
|
|
106
|
+
fx.ensureDir(skillDir);
|
|
107
|
+
fx.copyFile(srcPath, join(skillDir, 'SKILL.md'));
|
|
108
|
+
installedNow.add(skillName);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function collectSourceSkillNames() {
|
|
114
|
+
const names = new Set();
|
|
115
|
+
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
116
|
+
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
117
|
+
if (!existsSync(levelDir)) continue;
|
|
118
|
+
collectNamesFromDir(levelDir, names);
|
|
119
|
+
}
|
|
120
|
+
return names;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function collectNamesFromDir(dir, names) {
|
|
124
|
+
for (const entry of readdirSync(dir)) {
|
|
125
|
+
const fullPath = join(dir, entry);
|
|
126
|
+
if (!statSync(fullPath).isDirectory()) continue;
|
|
127
|
+
if (existsSync(join(fullPath, 'SKILL.md'))) {
|
|
128
|
+
names.add(entry);
|
|
129
|
+
} else {
|
|
130
|
+
collectNamesFromDir(fullPath, names);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Install morph framework skills to .claude/skills/ in the target project.
|
|
137
|
+
* Skills are copied into subdirectories as SKILL.md files so Claude Code can
|
|
138
|
+
* discover them natively via /<skill-name> syntax (e.g., /phase-design,
|
|
139
|
+
* /nextjs-expert). Directory-based skills also get their scripts/,
|
|
140
|
+
* references/, and assets/ subdirectories copied for on-demand loading.
|
|
141
|
+
*
|
|
142
|
+
* Stack filtering: if projectTags is provided, only skills whose `stacks:`
|
|
143
|
+
* frontmatter matches the project tags are installed. Skills without a
|
|
144
|
+
* `stacks:` field are always installed (universal).
|
|
145
|
+
*
|
|
146
|
+
* @param {string} projectDir - Target project root directory
|
|
147
|
+
* @param {Object} [options]
|
|
148
|
+
* @param {string[]} [options.projectTags=[]] - Stack tags from config
|
|
149
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
150
|
+
* Coletor de efeitos; o default executa e registra numa lista descartada.
|
|
151
|
+
*/
|
|
152
|
+
export async function installSkills(projectDir, { projectTags = [], fx = realEffects() } = {}) {
|
|
153
|
+
const claudeSkillsDir = join(projectDir, '.claude', 'skills');
|
|
154
|
+
fx.ensureDir(claudeSkillsDir);
|
|
155
|
+
|
|
156
|
+
// Skill names this call actually (re)installs, given projectTags. Tracked
|
|
157
|
+
// as we go (NOT by re-scanning claudeSkillsDir afterwards) — a disk scan
|
|
158
|
+
// can't tell "installed this run" apart from "leftover from a previous
|
|
159
|
+
// run with different projectTags", which is exactly the stale entry a
|
|
160
|
+
// stack change (or a re-run that narrows the stack) needs cleaned up.
|
|
161
|
+
const installedNow = new Set();
|
|
162
|
+
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
163
|
+
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
164
|
+
if (!existsSync(levelDir)) continue;
|
|
165
|
+
installSkillsFromDir(levelDir, claudeSkillsDir, projectTags, installedNow, fx);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Clean-slate: remove any skill dir that is a KNOWN framework skill (still
|
|
169
|
+
// present in framework/skills/core/) but did NOT get (re)installed this
|
|
170
|
+
// run — i.e. it was filtered out by a `stacks:` mismatch (project.stack
|
|
171
|
+
// changed, or a re-run narrowed the tags). Previously scoped to `morph-`
|
|
172
|
+
// prefixed names only, which silently exempted every non-`morph-` skill
|
|
173
|
+
// (the Umbraco pair) from ever being cleaned up: install once with
|
|
174
|
+
// `umbraco` in the stack, drop the stack later, and they'd survive every
|
|
175
|
+
// reinstall/update forever. Names outside allSourceSkills (user-added,
|
|
176
|
+
// non-framework skills) are never touched.
|
|
177
|
+
//
|
|
178
|
+
// Em `dryRun` este laço NÃO emite nada, e a razão está na D6: numa execução
|
|
179
|
+
// real do `update` ele sempre vê um diretório VAZIO, porque
|
|
180
|
+
// `cleanFrameworkDirs()` acabou de apagar `.claude/skills/` inteiro. Num dry
|
|
181
|
+
// ingênuo nada foi apagado, ele veria o diretório POPULADO, e o relatório
|
|
182
|
+
// preveria remoções que a execução real nunca faz — errado na direção que
|
|
183
|
+
// assusta o usuário. O clean subsome este laço, provadamente.
|
|
184
|
+
if (!fx.dryRun) {
|
|
185
|
+
const allSourceSkills = collectSourceSkillNames();
|
|
186
|
+
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
187
|
+
if (!installedNow.has(entry) && allSourceSkills.has(entry)) {
|
|
188
|
+
fx.remove(join(claudeSkillsDir, entry));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return { installed: installedNow.size };
|
|
194
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { pathExists, readJson
|
|
4
|
+
import { pathExists, readJson } from './file-copier.js';
|
|
5
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
|
|
@@ -58,14 +59,14 @@ export async function getProjectMorphVersion(projectPath) {
|
|
|
58
59
|
* @param {string} version - Version to save
|
|
59
60
|
* @returns {Promise<void>}
|
|
60
61
|
*/
|
|
61
|
-
export async function saveProjectMorphVersion(projectPath, version) {
|
|
62
|
+
export async function saveProjectMorphVersion(projectPath, version, fx = realEffects()) {
|
|
62
63
|
const versionPath = join(projectPath, '.morph', '.morphversion');
|
|
63
64
|
const versionInfo = {
|
|
64
65
|
morphVersion: version,
|
|
65
66
|
installedAt: new Date().toISOString(),
|
|
66
67
|
cliVersion: version
|
|
67
68
|
};
|
|
68
|
-
|
|
69
|
+
fx.writeJson(versionPath, versionInfo);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
/**
|
|
@@ -91,6 +92,70 @@ export function compareVersions(v1, v2) {
|
|
|
91
92
|
return 0;
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Major de uma versão, ou `null` quando a string não permite decidir.
|
|
97
|
+
*
|
|
98
|
+
* Vive aqui, ao lado de `compareVersions`, de propósito: uma segunda
|
|
99
|
+
* aritmética de versão noutro arquivo é exatamente como duas verdades sobre a
|
|
100
|
+
* mesma versão nascem.
|
|
101
|
+
*
|
|
102
|
+
* @param {string|null|undefined} version
|
|
103
|
+
* @returns {number|null}
|
|
104
|
+
*/
|
|
105
|
+
export function majorOf(version) {
|
|
106
|
+
if (typeof version !== 'string') return null;
|
|
107
|
+
const m = /^\s*v?(\d+)\./.exec(version) ?? /^\s*v?(\d+)\s*$/.exec(version);
|
|
108
|
+
return m ? Number(m[1]) : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Limiar do freio: um salto de DOIS majors ou mais recusa sem `--force`.
|
|
113
|
+
*
|
|
114
|
+
* O limiar não é "quantos majors dá para pular com segurança" — é "a partir de
|
|
115
|
+
* onde o usuário merece ler o relatório antes". Medido: um projeto em 7.x indo
|
|
116
|
+
* para 9.x recusa; um em 8.x atualiza sem atrito.
|
|
117
|
+
*/
|
|
118
|
+
export const MAJOR_JUMP_LIMIT = 2;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Avalia o salto entre a versão instalada no projeto e a versão de destino.
|
|
122
|
+
*
|
|
123
|
+
* Três resultados, e a diferença entre eles é o que a mensagem tem de dizer:
|
|
124
|
+
*
|
|
125
|
+
* - `reason: 'unknown-installed'` — a versão instalada é indeterminável.
|
|
126
|
+
* Tratada como salto grande, com motivo PRÓPRIO: "não sei quão velho isto é"
|
|
127
|
+
* é precisamente quando sobrescrever às cegas é mais perigoso, e tratá-la
|
|
128
|
+
* como zero seria a única leitura que libera o pior caso.
|
|
129
|
+
* - `reason: 'major-jump'` — salto medido de `jump` majors, >= ao limiar.
|
|
130
|
+
* - `reason: null` — sem freio (mesmo major, um só major de diferença, ou
|
|
131
|
+
* destino não superior à instalada).
|
|
132
|
+
*
|
|
133
|
+
* @param {string|null} installedVersion
|
|
134
|
+
* @param {string|null} targetVersion
|
|
135
|
+
* @param {{ limit?: number }} [options]
|
|
136
|
+
* @returns {{ refuse: boolean, reason: 'unknown-installed'|'major-jump'|null, jump: number|null }}
|
|
137
|
+
*/
|
|
138
|
+
export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_LIMIT } = {}) {
|
|
139
|
+
const installedMajor = majorOf(installedVersion);
|
|
140
|
+
const targetMajor = majorOf(targetVersion);
|
|
141
|
+
|
|
142
|
+
if (installedMajor === null) {
|
|
143
|
+
return { refuse: true, reason: 'unknown-installed', jump: null };
|
|
144
|
+
}
|
|
145
|
+
if (targetMajor === null) {
|
|
146
|
+
// Sem destino legível não há salto a medir — e inventar um seria decidir
|
|
147
|
+
// por um número que ninguém mantém.
|
|
148
|
+
return { refuse: false, reason: null, jump: null };
|
|
149
|
+
}
|
|
150
|
+
if (compareVersions(installedVersion, targetVersion) >= 0) {
|
|
151
|
+
return { refuse: false, reason: null, jump: 0 };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const jump = targetMajor - installedMajor;
|
|
155
|
+
if (jump >= limit) return { refuse: true, reason: 'major-jump', jump };
|
|
156
|
+
return { refuse: false, reason: null, jump };
|
|
157
|
+
}
|
|
158
|
+
|
|
94
159
|
/**
|
|
95
160
|
* Detect how the CLI was installed
|
|
96
161
|
* @returns {'global'|'npx'|'unknown'}
|