@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
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
* Evaluator) are assumed by Claude itself per MORPH.md, not as separate agents.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { readFileSync,
|
|
24
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
25
25
|
import { join } from 'path';
|
|
26
26
|
import { shouldInstall } from '../lib/stack-filter.js';
|
|
27
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
27
28
|
|
|
28
29
|
/** Default tool scope for domain personas (override per agent via agents.json `tools`). */
|
|
29
30
|
export const DEFAULT_AGENT_TOOLS = 'Read, Write, Edit, Bash, Glob, Grep';
|
|
@@ -79,10 +80,12 @@ const MANAGED_HEADER =
|
|
|
79
80
|
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
80
81
|
* @param {object} options
|
|
81
82
|
* @param {string[]} [options.projectTags=[]] - Stack tags from config (e.g. ['dotnet', 'nextjs'])
|
|
83
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
84
|
+
* Coletor de efeitos; o default executa e registra numa lista descartada.
|
|
82
85
|
* @returns {Promise<{ installed: number }>}
|
|
83
86
|
*/
|
|
84
87
|
export async function installAgents(projectDir, frameworkDir = 'framework', options = {}) {
|
|
85
|
-
const { projectTags = [] } = options;
|
|
88
|
+
const { projectTags = [], fx = realEffects() } = options;
|
|
86
89
|
|
|
87
90
|
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
88
91
|
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
|
|
@@ -93,7 +96,7 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
93
96
|
.map(([id, data]) => ({ id, ...data }));
|
|
94
97
|
|
|
95
98
|
const targetDir = join(projectDir, '.claude', 'agents');
|
|
96
|
-
|
|
99
|
+
fx.ensureDir(targetDir);
|
|
97
100
|
|
|
98
101
|
const knownMcpIds = loadKnownMcpIds(frameworkDir);
|
|
99
102
|
|
|
@@ -111,7 +114,7 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
111
114
|
const filename = `morph-${agent.id}.md`;
|
|
112
115
|
const targetPath = join(targetDir, filename);
|
|
113
116
|
|
|
114
|
-
|
|
117
|
+
fx.writeFile(targetPath, renderAgentMd(agent, knownMcpIds));
|
|
115
118
|
installedFilenames.add(filename);
|
|
116
119
|
installedAgents.push({
|
|
117
120
|
id: agent.id,
|
|
@@ -122,10 +125,17 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
122
125
|
installed++;
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
// Cleanup orphan morph-* files filtered out by stack
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
// Cleanup orphan morph-* files filtered out by stack.
|
|
129
|
+
//
|
|
130
|
+
// Calado em `dryRun` pelo mesmo motivo do laço gêmeo em skills-installer.js
|
|
131
|
+
// (D6): numa execução real do `update` este diretório acabou de ser apagado
|
|
132
|
+
// inteiro por `cleanFrameworkDirs()`, então o laço nunca remove nada. Num
|
|
133
|
+
// dry ingênuo ele veria o diretório antigo e preveria remoções inexistentes.
|
|
134
|
+
if (!fx.dryRun) {
|
|
135
|
+
for (const file of readdirSync(targetDir)) {
|
|
136
|
+
if (allPossible.has(file) && !installedFilenames.has(file)) {
|
|
137
|
+
fx.remove(join(targetDir, file));
|
|
138
|
+
}
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* The root CLAUDE.md belongs to the USER — morph-spec only adds its import.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { pathExists, readFile
|
|
11
|
+
import { pathExists, readFile } from './file-copier.js';
|
|
12
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
12
13
|
|
|
13
14
|
const MORPH_IMPORT = '@.claude/CLAUDE.md';
|
|
14
15
|
const MORPH_MARKER = 'MORPH-SPEC';
|
|
@@ -24,11 +25,15 @@ const MORPH_MARKER = 'MORPH-SPEC';
|
|
|
24
25
|
* Idempotent — does nothing if the import already exists.
|
|
25
26
|
*
|
|
26
27
|
* @param {string} claudeMdPath - Absolute path to root CLAUDE.md
|
|
28
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
|
|
29
|
+
* Coletor de efeitos; o default executa e registra numa lista descartada.
|
|
30
|
+
* A DECISÃO (criar / migrar / anexar / nada) sai de uma LEITURA, que
|
|
31
|
+
* acontece igual nos dois modos — por isso previsão e execução concordam.
|
|
27
32
|
* @returns {Promise<'created'|'migrated'|'injected'|'already-present'>}
|
|
28
33
|
*/
|
|
29
|
-
export async function injectMorphImport(claudeMdPath) {
|
|
34
|
+
export async function injectMorphImport(claudeMdPath, fx = realEffects()) {
|
|
30
35
|
if (!await pathExists(claudeMdPath)) {
|
|
31
|
-
|
|
36
|
+
fx.writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
|
|
32
37
|
return 'created';
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -39,13 +44,13 @@ export async function injectMorphImport(claudeMdPath) {
|
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
if (isMorphOnly(content)) {
|
|
42
|
-
|
|
47
|
+
fx.writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
|
|
43
48
|
return 'migrated';
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
// User has their own content — append the import section
|
|
47
52
|
const separator = content.endsWith('\n') ? '\n' : '\n\n';
|
|
48
|
-
|
|
53
|
+
fx.writeFile(claudeMdPath, `${content}${separator}## MORPH-SPEC\n\n${MORPH_IMPORT}\n`);
|
|
49
54
|
return 'injected';
|
|
50
55
|
}
|
|
51
56
|
|
package/src/utils/file-copier.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import { join, dirname, resolve } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { WORKTREE_DIR_NAMES } from '../lib/worktree/dirs.js';
|
|
5
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
4
6
|
|
|
5
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
8
|
const frameworkRoot = join(__dirname, '..', '..');
|
|
@@ -137,12 +139,12 @@ export async function createDirectoryLink(target, link) {
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
/**
|
|
143
|
+
* The morph-owned block of the installed .gitignore. Exported so the worktree
|
|
144
|
+
* container canary (test/lib/worktree/dirs.test.js) can prove both names are
|
|
145
|
+
* covered without rendering a file.
|
|
146
|
+
*/
|
|
147
|
+
export const MORPH_GITIGNORE_ENTRIES = [
|
|
146
148
|
// Derivable, per-machine state index — rebuilt from the committed
|
|
147
149
|
// .morph/features/{feature}/feature.json files. Never commit it: doing so
|
|
148
150
|
// sweeps every feature's accumulated diffs into one commit. The feature
|
|
@@ -165,19 +167,29 @@ export async function updateGitignore(projectPath) {
|
|
|
165
167
|
'.claude/agents/',
|
|
166
168
|
'.claude/CLAUDE.md',
|
|
167
169
|
'.claude/settings.local.json',
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
170
|
+
// Both container names, anchored at the root. The legacy name (pre-C1) is
|
|
171
|
+
// kept so a project that already has `.worktrees/` on disk does not
|
|
172
|
+
// suddenly see it as untracked dirt — which would also make `finish`'s
|
|
173
|
+
// clean-root check refuse.
|
|
174
|
+
...WORKTREE_DIR_NAMES.map((d) => `/${d}/`),
|
|
173
175
|
'.playwright-mcp/',
|
|
174
176
|
'docs/superpowers/',
|
|
175
177
|
// Grafo de codebase — artefato derivado, reconstruído pelo hook do
|
|
176
178
|
// graphify a cada commit. Committá-lo faria de todo commit um diff de
|
|
177
179
|
// megabytes de JSON gerado.
|
|
178
180
|
'graphify-out/',
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {string} projectPath
|
|
185
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
|
|
186
|
+
* Coletor de efeitos. O default executa e registra numa lista descartada, de
|
|
187
|
+
* modo que todo chamador antigo (`setup-infra`, `init`) siga idêntico.
|
|
188
|
+
*/
|
|
189
|
+
export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
190
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
191
|
+
|
|
192
|
+
const morphRules = ['', '# MORPH-SPEC', ...MORPH_GITIGNORE_ENTRIES, ''];
|
|
181
193
|
|
|
182
194
|
let content = '';
|
|
183
195
|
let hasMorphSection = false;
|
|
@@ -223,7 +235,7 @@ export async function updateGitignore(projectPath) {
|
|
|
223
235
|
content += morphRules.join('\n');
|
|
224
236
|
}
|
|
225
237
|
|
|
226
|
-
|
|
238
|
+
fx.writeFile(gitignorePath, content);
|
|
227
239
|
}
|
|
228
240
|
|
|
229
241
|
/**
|
|
@@ -243,7 +255,7 @@ export async function updateGitignore(projectPath) {
|
|
|
243
255
|
*
|
|
244
256
|
* @param {string} projectPath
|
|
245
257
|
*/
|
|
246
|
-
export async function ensureGitattributes(projectPath) {
|
|
258
|
+
export async function ensureGitattributes(projectPath, fx = realEffects()) {
|
|
247
259
|
const gitattributesPath = join(projectPath, '.gitattributes');
|
|
248
260
|
const MARKER = '# MORPH-SPEC';
|
|
249
261
|
const morphBlock = [
|
|
@@ -262,7 +274,7 @@ export async function ensureGitattributes(projectPath) {
|
|
|
262
274
|
content += '\n';
|
|
263
275
|
}
|
|
264
276
|
content += `${morphBlock.join('\n')}\n`;
|
|
265
|
-
|
|
277
|
+
fx.writeFile(gitattributesPath, content);
|
|
266
278
|
}
|
|
267
279
|
|
|
268
280
|
/**
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { join, dirname } from 'path';
|
|
12
|
-
import { readFile
|
|
12
|
+
import { readFile } from 'fs/promises';
|
|
13
13
|
import { existsSync, chmodSync, readdirSync } from 'fs';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Current hooks schema version — bump when hook definitions change.
|
|
@@ -27,8 +28,15 @@ import { execSync } from 'child_process';
|
|
|
27
28
|
* (`copyDirectory` defaults to `overwrite: true`), so the bump does not gate
|
|
28
29
|
* delivery — it makes `morph-spec update` report the change honestly instead
|
|
29
30
|
* of "already current".
|
|
31
|
+
*
|
|
32
|
+
* 3.8.0 (worktree governance) registers Claude Code's WorktreeCreate /
|
|
33
|
+
* WorktreeRemove events — the native `claude -w` / EnterWorktree / Agent
|
|
34
|
+
* isolation worktree is replaced by `morph-spec worktree setup|provision`
|
|
35
|
+
* (junctions, port block, primary-root landing) and its removal routed
|
|
36
|
+
* through the safe `worktree remove --if-clean` — and appends
|
|
37
|
+
* block-raw-worktree-add to the PreToolUse:Bash chain.
|
|
30
38
|
*/
|
|
31
|
-
const HOOKS_VERSION = '3.
|
|
39
|
+
const HOOKS_VERSION = '3.8.0';
|
|
32
40
|
|
|
33
41
|
/** Marker for old dispatch.js (v1) */
|
|
34
42
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -137,14 +145,50 @@ const MORPH_HOOKS = [
|
|
|
137
145
|
// block-destructive-worktree-removal (dynamic: scans the target for reparse
|
|
138
146
|
// points), so the cheap check short-circuits the filesystem walk. The second
|
|
139
147
|
// one is the only defense against the raw-removal vector that does not depend
|
|
140
|
-
// on someone having read MORPH.md.
|
|
148
|
+
// on someone having read MORPH.md. block-raw-worktree-add is its way-IN
|
|
149
|
+
// counterpart (a raw `git worktree add` builds a tree with no junctions, no
|
|
150
|
+
// ports, no identity) — static, so it costs nothing after the first two.
|
|
151
|
+
// worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
|
|
152
|
+
// with the worktree's port block, and a command a previous link blocked must
|
|
153
|
+
// never be rewritten. Block-first-wins guarantees it never sees one.
|
|
141
154
|
{
|
|
142
155
|
event: 'PreToolUse',
|
|
143
156
|
matcher: 'Bash',
|
|
144
157
|
hooks: [{
|
|
145
158
|
type: 'command',
|
|
146
159
|
command: 'node framework/hooks/claude-code/core/chain.js',
|
|
147
|
-
args: ['pre-bash-denylist', 'block-destructive-worktree-removal']
|
|
160
|
+
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'worktree-env-inject']
|
|
161
|
+
}]
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// === WorktreeCreate — REPLACES Claude Code's native `git worktree add` for
|
|
165
|
+
// `claude -w`, the EnterWorktree tool and `Agent isolation:"worktree"`
|
|
166
|
+
// (measured: all three fire it). The hook prints the created path as its
|
|
167
|
+
// last stdout line; a non-zero exit fails the creation. No matcher exists
|
|
168
|
+
// for this event. 120s: it spawns the morph-spec CLI (setup or provision),
|
|
169
|
+
// which runs git, links infra, allocates ports and materializes state. ===
|
|
170
|
+
{
|
|
171
|
+
event: 'WorktreeCreate',
|
|
172
|
+
matcher: null,
|
|
173
|
+
hooks: [{
|
|
174
|
+
type: 'command',
|
|
175
|
+
command: 'node framework/hooks/claude-code/core/worktree-create.js',
|
|
176
|
+
timeout: 120000
|
|
177
|
+
}]
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// === WorktreeRemove — routes Claude Code's own cleanup through
|
|
181
|
+
// `morph-spec worktree remove --target --if-clean` (junctions dropped BEFORE
|
|
182
|
+
// git touches the tree; a dirty tree is left alone). Output ignored, always
|
|
183
|
+
// exit 0. Secondary net: in headless runs the event was observed not to
|
|
184
|
+
// fire — block-destructive-worktree-removal remains the primary defense. ===
|
|
185
|
+
{
|
|
186
|
+
event: 'WorktreeRemove',
|
|
187
|
+
matcher: null,
|
|
188
|
+
hooks: [{
|
|
189
|
+
type: 'command',
|
|
190
|
+
command: 'node framework/hooks/claude-code/core/worktree-remove.js',
|
|
191
|
+
timeout: 60000
|
|
148
192
|
}]
|
|
149
193
|
},
|
|
150
194
|
|
|
@@ -237,9 +281,14 @@ const MORPH_HOOKS = [
|
|
|
237
281
|
* - Tracks installed version via _morph_managed.hooks_version
|
|
238
282
|
*
|
|
239
283
|
* @param {string} targetPath - Project root directory
|
|
284
|
+
* @param {object} [options]
|
|
285
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
286
|
+
* Coletor de efeitos. Esta função é o seam mais barato do caminho de update:
|
|
287
|
+
* o objeto de settings INTEIRO já é montado em memória e há UMA escrita, no
|
|
288
|
+
* fim. Previsão e execução percorrem a mesma montagem.
|
|
240
289
|
* @returns {Promise<{ installed: number, updated: boolean }>}
|
|
241
290
|
*/
|
|
242
|
-
export async function installClaudeHooks(targetPath) {
|
|
291
|
+
export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}) {
|
|
243
292
|
const claudeDir = join(targetPath, '.claude');
|
|
244
293
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
245
294
|
|
|
@@ -366,11 +415,9 @@ export async function installClaudeHooks(targetPath) {
|
|
|
366
415
|
settings._morph_managed.generated_at = new Date().toISOString();
|
|
367
416
|
|
|
368
417
|
// Ensure .claude directory exists
|
|
369
|
-
|
|
370
|
-
await mkdir(claudeDir, { recursive: true });
|
|
371
|
-
}
|
|
418
|
+
fx.ensureDir(claudeDir);
|
|
372
419
|
|
|
373
|
-
|
|
420
|
+
fx.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
374
421
|
return { installed, updated: !alreadyCurrent };
|
|
375
422
|
}
|
|
376
423
|
|
|
@@ -487,19 +534,24 @@ function resolvePythonForStatusline() {
|
|
|
487
534
|
*
|
|
488
535
|
* @param {string} hooksSourceDir - Path to framework/hooks/claude-code/
|
|
489
536
|
* @param {string} [globalClaudeDirOverride] - Override ~/.claude/ (for testing)
|
|
537
|
+
* @param {object} [options]
|
|
538
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
539
|
+
* Coletor de efeitos. Estes são os efeitos FORA do projeto — o relatório do
|
|
540
|
+
* `--dry-run` é obrigado a nomeá-los separadamente, porque ninguém os
|
|
541
|
+
* adivinha da palavra "update".
|
|
490
542
|
* @returns {Promise<{ installed: boolean, globalDir: string }>}
|
|
491
543
|
*/
|
|
492
|
-
export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null) {
|
|
544
|
+
export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null, { fx = realEffects() } = {}) {
|
|
493
545
|
const globalDir = globalClaudeDirOverride || join(homedir(), '.claude');
|
|
494
|
-
|
|
546
|
+
fx.ensureDir(globalDir);
|
|
495
547
|
|
|
496
548
|
// Copy statusline scripts to ~/.claude/
|
|
497
549
|
const scripts = ['statusline.sh', 'statusline.py'];
|
|
498
550
|
for (const fname of scripts) {
|
|
499
551
|
const src = join(hooksSourceDir, fname);
|
|
500
552
|
if (existsSync(src)) {
|
|
501
|
-
|
|
502
|
-
if (process.platform !== 'win32') {
|
|
553
|
+
fx.copyFile(src, join(globalDir, fname));
|
|
554
|
+
if (process.platform !== 'win32' && !fx.dryRun) {
|
|
503
555
|
try { chmodSync(join(globalDir, fname), 0o755); } catch { /* non-critical */ }
|
|
504
556
|
}
|
|
505
557
|
}
|
|
@@ -507,14 +559,6 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
507
559
|
|
|
508
560
|
// Register statusLine in ~/.claude/settings.json (global user settings)
|
|
509
561
|
const settingsPath = join(globalDir, 'settings.json');
|
|
510
|
-
let settings = {};
|
|
511
|
-
if (existsSync(settingsPath)) {
|
|
512
|
-
try {
|
|
513
|
-
settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
514
|
-
} catch {
|
|
515
|
-
settings = {};
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
562
|
|
|
519
563
|
// On Windows, Claude Code cannot execute .sh files directly — call Python instead.
|
|
520
564
|
const statuslineCommand = process.platform === 'win32'
|
|
@@ -526,14 +570,16 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
526
570
|
// `↻` countdown to the 5h rate-limit reset — the only widget that moves on
|
|
527
571
|
// its own — sits frozen through a long turn. Everything else is event-driven
|
|
528
572
|
// and would be fine without this.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
573
|
+
// Fusão rasa: só `statusLine` é do morph-spec; tudo o mais no
|
|
574
|
+
// `~/.claude/settings.json` é do usuário e sobrevive intocado.
|
|
575
|
+
fx.mergeJson(settingsPath, {
|
|
576
|
+
statusLine: {
|
|
577
|
+
type: 'command',
|
|
578
|
+
command: statuslineCommand,
|
|
579
|
+
padding: 2,
|
|
580
|
+
refreshInterval: 10
|
|
581
|
+
}
|
|
582
|
+
});
|
|
537
583
|
return { installed: true, globalDir };
|
|
538
584
|
}
|
|
539
585
|
|