@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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* worktree-remove.js — WorktreeRemove hook
|
|
4
|
+
*
|
|
5
|
+
* Event: WorktreeRemove (Claude Code's own cleanup of a tree it created via
|
|
6
|
+
* `claude -w` / EnterWorktree / Agent isolation). Input carries
|
|
7
|
+
* `worktree_path` (absolute). No decision control, output ignored — this hook
|
|
8
|
+
* ALWAYS exits 0.
|
|
9
|
+
*
|
|
10
|
+
* Why it exists: Claude Code's native removal is a raw `git worktree remove`,
|
|
11
|
+
* which FOLLOWS the infra junctions the WorktreeCreate hook put in that tree
|
|
12
|
+
* and empties the primary root's `.claude/` and `.morph/framework/`. This
|
|
13
|
+
* hook routes the removal through `morph-spec worktree remove --target
|
|
14
|
+
* --if-clean` instead: junctions dropped first, PROVE-or-DECLINE, and a tree
|
|
15
|
+
* holding uncommitted work is left alone (Claude Code's notion of "clean" is
|
|
16
|
+
* not the user's).
|
|
17
|
+
*
|
|
18
|
+
* Secondary net: in headless runs the event was observed NOT to fire; the
|
|
19
|
+
* PreToolUse guard (block-destructive-worktree-removal) stays the primary
|
|
20
|
+
* defense. Fail-open everywhere — diagnostics to stderr, never a throw, never
|
|
21
|
+
* a non-zero exit.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { existsSync } from 'fs';
|
|
25
|
+
import { execFileSync } from 'child_process';
|
|
26
|
+
import { dirname, resolve } from 'path';
|
|
27
|
+
import { stateExists } from '../../shared/state-reader.js';
|
|
28
|
+
import { resolveGitCommonDir } from '../../shared/git-paths.js';
|
|
29
|
+
import { runMorphSpec } from '../../shared/morph-cli.js';
|
|
30
|
+
|
|
31
|
+
const fwd = (p) => String(p).replace(/\\/g, '/');
|
|
32
|
+
|
|
33
|
+
function primaryRoot(startDir) {
|
|
34
|
+
try {
|
|
35
|
+
const out = execFileSync('git', ['worktree', 'list', '--porcelain'], { cwd: startDir, stdio: ['ignore', 'pipe', 'pipe'] }).toString();
|
|
36
|
+
const first = out.split('\n').find((l) => l.startsWith('worktree '));
|
|
37
|
+
if (first) return fwd(first.slice('worktree '.length).trim());
|
|
38
|
+
} catch { /* fall through */ }
|
|
39
|
+
const common = resolveGitCommonDir(startDir);
|
|
40
|
+
return common ? fwd(dirname(common)) : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} payload - parsed WorktreeRemove stdin payload ({ cwd, worktree_path })
|
|
45
|
+
* @returns {Promise<null>} always null — the event ignores output
|
|
46
|
+
*/
|
|
47
|
+
export async function run(payload) {
|
|
48
|
+
try {
|
|
49
|
+
const target = payload?.worktree_path;
|
|
50
|
+
if (!target || !existsSync(target)) return null;
|
|
51
|
+
|
|
52
|
+
const start = payload?.cwd || process.env.CLAUDE_PROJECT_DIR || dirname(resolve(target));
|
|
53
|
+
const root = primaryRoot(start) || primaryRoot(dirname(resolve(target)));
|
|
54
|
+
if (!root) return null;
|
|
55
|
+
// Nothing to govern outside a morph project — and never a raw removal
|
|
56
|
+
// from here either: the tree is simply left for git's own cleanup.
|
|
57
|
+
if (!stateExists(root)) return null;
|
|
58
|
+
|
|
59
|
+
const r = runMorphSpec(['worktree', 'remove', '--target', fwd(resolve(target)), '--if-clean', '--json'], { cwd: root });
|
|
60
|
+
if (r.unavailable) {
|
|
61
|
+
process.stderr.write(`worktree-remove: morph-spec CLI not found — "${target}" left in place (remove it with \`morph-spec worktree remove --target\`, never with raw git: it follows the infra junctions out).\n`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (!r.ok) {
|
|
65
|
+
const why = r.json?.error || r.json?.hint || r.stderr?.trim() || `exit ${r.code}`;
|
|
66
|
+
process.stderr.write(`worktree-remove: "${target}" NOT removed — ${why}\n`);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
process.stderr.write(`worktree-remove: ${err?.message || err}\n`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Standalone tail: output is ignored by this event, so nothing is printed;
|
|
76
|
+
// the exit code is always 0.
|
|
77
|
+
if (!process.env.MORPH_HOOK_CHAINED) {
|
|
78
|
+
const { readStdin } = await import('../../shared/stdin-reader.js');
|
|
79
|
+
try { await run(await readStdin()); } catch { /* fail-open */ }
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* command-tokens.js — shell-ish tokenizer shared by the PreToolUse Bash guards.
|
|
3
|
+
*
|
|
4
|
+
* Lives in shared/ (not inside a hook) on purpose: every core hook ends in a
|
|
5
|
+
* standalone tail that reads stdin and exits, so a hook importing a sibling
|
|
6
|
+
* hook for a helper would fire THAT tail in its own process — consuming the
|
|
7
|
+
* payload and exiting before the importer ever ran. block-raw-worktree-add
|
|
8
|
+
* and block-destructive-worktree-removal both read paths off these tokens,
|
|
9
|
+
* so they agree on what a quoted path is.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Split a shell-ish command into tokens, honoring single and double quotes.
|
|
14
|
+
* @param {string} command
|
|
15
|
+
* @returns {string[]}
|
|
16
|
+
*/
|
|
17
|
+
export function tokenize(command) {
|
|
18
|
+
const tokens = [];
|
|
19
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
20
|
+
let m;
|
|
21
|
+
while ((m = re.exec(String(command || ''))) !== null) tokens.push(m[1] ?? m[2] ?? m[3]);
|
|
22
|
+
return tokens;
|
|
23
|
+
}
|
|
Binary file
|
|
@@ -58,6 +58,31 @@ function findDotGit(startDir) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Is `startDir` inside a LINKED (secondary) worktree?
|
|
63
|
+
*
|
|
64
|
+
* Same on-disk fact `resolveGitCommonDir` reads: git represents a linked
|
|
65
|
+
* worktree's `.git` as a FILE holding a `gitdir:` pointer, and a primary
|
|
66
|
+
* checkout's `.git` as a DIRECTORY. The e2e isolation rule keys on this —
|
|
67
|
+
* a compose that cannot be isolated is a hard failure only where a second
|
|
68
|
+
* stack could actually collide with the primary's (see e2e-check.js
|
|
69
|
+
* `decideIsolation`).
|
|
70
|
+
*
|
|
71
|
+
* Fail-open: outside any repo, or on an unreadable path, answers `false` —
|
|
72
|
+
* the lenient direction (warn instead of fail).
|
|
73
|
+
*
|
|
74
|
+
* @param {string} startDir
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
export function isSecondaryWorktree(startDir) {
|
|
78
|
+
try {
|
|
79
|
+
const found = findDotGit(startDir);
|
|
80
|
+
return Boolean(found && !found.isDirectory);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
/**
|
|
62
87
|
* Resolve the git COMMON dir for `startDir` — pure filesystem, no subprocess.
|
|
63
88
|
* Always returns an absolute path (unlike `git rev-parse --git-common-dir`,
|
|
@@ -122,3 +122,20 @@ export async function runStandalone(run) {
|
|
|
122
122
|
}
|
|
123
123
|
process.exit(0);
|
|
124
124
|
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Pure builder for a PreToolUse rewrite: Claude Code runs the tool with
|
|
128
|
+
* `updatedInput` in place of the original `tool_input`, and the decision is
|
|
129
|
+
* an explicit `allow` (a rewrite without a decision is ignored). Use it ONLY
|
|
130
|
+
* when the hook actually changed something — an unconditional `allow` would
|
|
131
|
+
* skip the permission prompt for every call it touches.
|
|
132
|
+
*
|
|
133
|
+
* @param {object} updatedInput - the full replacement tool_input
|
|
134
|
+
* @param {string} eventName - 'PreToolUse'
|
|
135
|
+
* @param {string} [context] - optional additionalContext (what was changed)
|
|
136
|
+
*/
|
|
137
|
+
export function updatedInputResult(updatedInput, eventName, context) {
|
|
138
|
+
const out = { hookEventName: eventName, permissionDecision: 'allow', updatedInput };
|
|
139
|
+
if (context) out.additionalContext = context;
|
|
140
|
+
return { hookSpecificOutput: out };
|
|
141
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* morph-cli.js — run the morph-spec CLI from inside a hook.
|
|
3
|
+
*
|
|
4
|
+
* Hooks are installed STANDALONE into `.morph/framework/hooks/` and may never
|
|
5
|
+
* import from `src/`; the only way for a hook to reuse a CLI command (the
|
|
6
|
+
* WorktreeCreate hook needs `worktree setup`/`provision`, WorktreeRemove needs
|
|
7
|
+
* `worktree remove --if-clean`) is to spawn the CLI as a subprocess. This
|
|
8
|
+
* module is the ONE place that knows how to find it.
|
|
9
|
+
*
|
|
10
|
+
* Resolution order:
|
|
11
|
+
* 1. `MORPH_SPEC_BIN` — an explicit command, possibly with leading args and
|
|
12
|
+
* quoted paths (`node "R:/my repo/bin/morph-spec.js"`). What tests and a
|
|
13
|
+
* framework checkout use.
|
|
14
|
+
* 2. `morph-spec` on PATH — the npm-installed bin. On win32 the shim is
|
|
15
|
+
* `morph-spec.cmd`, which Node refuses to spawn without a shell, so it is
|
|
16
|
+
* located by scanning PATH ourselves and then run through the shell with
|
|
17
|
+
* each arg quoted; on POSIX a plain `shell:false` spawn.
|
|
18
|
+
* 3. `npx --no-install morph-spec` — a project-local dependency.
|
|
19
|
+
*
|
|
20
|
+
* ENOENT (or the shell's "not recognized"/"could not determine executable")
|
|
21
|
+
* everywhere → `{ unavailable: true }`, never a throw: the caller decides what
|
|
22
|
+
* degraded behavior looks like.
|
|
23
|
+
*
|
|
24
|
+
* `json` is the LAST line of stdout that parses as a JSON object (the CLI's
|
|
25
|
+
* commands print exactly one JSON line; anything a stray console.log adds
|
|
26
|
+
* before it is skipped), or null.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { spawnSync } from 'child_process';
|
|
30
|
+
import { existsSync } from 'fs';
|
|
31
|
+
import { delimiter, join } from 'path';
|
|
32
|
+
|
|
33
|
+
/** Split a command string into argv, honoring single and double quotes. */
|
|
34
|
+
function splitCommand(command) {
|
|
35
|
+
const tokens = [];
|
|
36
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
37
|
+
let m;
|
|
38
|
+
while ((m = re.exec(command)) !== null) tokens.push(m[1] ?? m[2] ?? m[3]);
|
|
39
|
+
return tokens;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Quote one argument for cmd.exe (the only shell path this module uses). */
|
|
43
|
+
function quoteForCmd(arg) {
|
|
44
|
+
if (arg === '') return '""';
|
|
45
|
+
if (!/[\s"&|<>^()]/.test(arg)) return arg;
|
|
46
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** First PATH entry holding `file`, or null. */
|
|
50
|
+
function findOnPath(file, env) {
|
|
51
|
+
const raw = env.PATH || env.Path || env.path || '';
|
|
52
|
+
for (const dir of raw.split(delimiter)) {
|
|
53
|
+
if (!dir) continue;
|
|
54
|
+
const candidate = join(dir, file);
|
|
55
|
+
try { if (existsSync(candidate)) return candidate; } catch { /* unreadable entry */ }
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Last stdout line that parses as a JSON object, or null. */
|
|
61
|
+
export function lastJsonLine(stdout) {
|
|
62
|
+
const lines = String(stdout || '').split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
63
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
64
|
+
if (!lines[i].startsWith('{')) continue;
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(lines[i]);
|
|
67
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
68
|
+
} catch { /* not this line */ }
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The candidates to try, in order. Each is `{ file, args, shell }`.
|
|
75
|
+
* @param {NodeJS.ProcessEnv} env
|
|
76
|
+
*/
|
|
77
|
+
function candidates(env) {
|
|
78
|
+
const list = [];
|
|
79
|
+
const explicit = env.MORPH_SPEC_BIN && splitCommand(env.MORPH_SPEC_BIN);
|
|
80
|
+
if (explicit && explicit.length) {
|
|
81
|
+
list.push({ file: explicit[0], args: explicit.slice(1), shell: false, kind: 'env' });
|
|
82
|
+
}
|
|
83
|
+
if (process.platform === 'win32') {
|
|
84
|
+
const cmd = findOnPath('morph-spec.cmd', env);
|
|
85
|
+
if (cmd) list.push({ file: cmd, args: [], shell: true, kind: 'path' });
|
|
86
|
+
const npx = findOnPath('npx.cmd', env);
|
|
87
|
+
if (npx) list.push({ file: npx, args: ['--no-install', 'morph-spec'], shell: true, kind: 'npx' });
|
|
88
|
+
} else {
|
|
89
|
+
list.push({ file: 'morph-spec', args: [], shell: false, kind: 'path' });
|
|
90
|
+
list.push({ file: 'npx', args: ['--no-install', 'morph-spec'], shell: false, kind: 'npx' });
|
|
91
|
+
}
|
|
92
|
+
return list;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const NOT_FOUND_RE = /not recognized as an internal or external command|could not determine executable to run|command not found|ENOENT/i;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Run `morph-spec <args>` and return its result.
|
|
99
|
+
*
|
|
100
|
+
* @param {string[]} args - CLI argv after the program name
|
|
101
|
+
* @param {{ cwd?: string, timeoutMs?: number, env?: NodeJS.ProcessEnv }} [opts]
|
|
102
|
+
* @returns {{ ok: boolean, code: number|null, stdout: string, stderr: string, json: object|null, unavailable: boolean }}
|
|
103
|
+
*/
|
|
104
|
+
export function runMorphSpec(args, { cwd, timeoutMs = 120000, env } = {}) {
|
|
105
|
+
const runEnv = env || process.env;
|
|
106
|
+
const argv = Array.isArray(args) ? args.map(String) : [];
|
|
107
|
+
|
|
108
|
+
for (const cand of candidates(runEnv)) {
|
|
109
|
+
let r;
|
|
110
|
+
try {
|
|
111
|
+
const full = [...cand.args, ...argv];
|
|
112
|
+
r = cand.shell
|
|
113
|
+
? spawnSync(quoteForCmd(cand.file), full.map(quoteForCmd), { cwd, env: runEnv, encoding: 'utf8', timeout: timeoutMs, shell: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
114
|
+
: spawnSync(cand.file, full, { cwd, env: runEnv, encoding: 'utf8', timeout: timeoutMs, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err?.code === 'ENOENT') continue;
|
|
117
|
+
return { ok: false, code: null, stdout: '', stderr: String(err?.message || err), json: null, unavailable: false };
|
|
118
|
+
}
|
|
119
|
+
if (r.error) {
|
|
120
|
+
if (r.error.code === 'ENOENT') continue;
|
|
121
|
+
return { ok: false, code: null, stdout: r.stdout || '', stderr: String(r.error.message || r.error), json: null, unavailable: false };
|
|
122
|
+
}
|
|
123
|
+
const stdout = r.stdout || '';
|
|
124
|
+
const stderr = r.stderr || '';
|
|
125
|
+
// The npx/shell paths cannot surface ENOENT: the shell (or npx itself)
|
|
126
|
+
// reports "not found" as a non-zero exit with a message. Only those two
|
|
127
|
+
// kinds are read that way — a real morph-spec failure is never mistaken
|
|
128
|
+
// for absence.
|
|
129
|
+
if (cand.kind !== 'env' && r.status !== 0 && !stdout.trim() && NOT_FOUND_RE.test(stderr)) continue;
|
|
130
|
+
return { ok: r.status === 0, code: r.status, stdout, stderr, json: lastJsonLine(stdout), unavailable: false };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { ok: false, code: null, stdout: '', stderr: 'morph-spec CLI not found (MORPH_SPEC_BIN, PATH, npx)', json: null, unavailable: true };
|
|
134
|
+
}
|
|
@@ -17,7 +17,7 @@ stacks:
|
|
|
17
17
|
@.morph/framework/standards/ai-agents/providers/model-registry.md
|
|
18
18
|
@.morph/framework/standards/ai-agents/structured-output.md
|
|
19
19
|
@.morph/framework/standards/ai-agents/multi-agent-patterns.md
|
|
20
|
-
@.morph/framework/standards/ai-agents/
|
|
20
|
+
@.morph/framework/standards/ai-agents/prompt-sources.md
|
|
21
21
|
@.morph/framework/standards/ai-agents/rag-custom-pgvector.md
|
|
22
22
|
@.morph/framework/standards/ai-agents/middleware-patterns.md
|
|
23
23
|
@.morph/framework/standards/ai-agents/observability-patterns.md
|
|
@@ -34,7 +34,9 @@ stacks:
|
|
|
34
34
|
| Standard | Carregue quando a feature envolve |
|
|
35
35
|
|----------|-----------------------------------|
|
|
36
36
|
| `ai-agents/modalities-vision.md` | imagem → texto (OCR, análise de documento, visão) |
|
|
37
|
-
| `ai-agents/modalities-image-gen.md` |
|
|
37
|
+
| `ai-agents/modalities-image-gen.md` | gerar/editar imagem em runtime: catálogo, marketing, variação de produto — aliases pinados, custo por chamada, deprecações |
|
|
38
|
+
| `ai-agents/media-video.md` | gerar vídeo em runtime: Veo via `Google.GenAI`, provedores só-HTTP, custo por segundo, o arquivo que expira |
|
|
39
|
+
| `ai-agents/media-pipeline.md` | **qualquer** feature que gere mídia: a regra dura (nunca no turno de conversa), estados, revisão humana, proveniência, marcação (AI Act art. 50) |
|
|
38
40
|
| `ai-agents/modalities-speech-to-text.md` | áudio → texto (transcrição, legendagem) |
|
|
39
41
|
| `ai-agents/modalities-text-to-speech.md` | texto → áudio (síntese de voz) |
|
|
40
42
|
| `ai-agents/batching.md` | processar N prompts independentes em lote (Batch API) |
|
|
@@ -43,4 +45,14 @@ stacks:
|
|
|
43
45
|
| `ai-agents/context-providers.md` | injeção dinâmica de contexto (`AIContextProvider`) |
|
|
44
46
|
| `ai-agents/mcp-tools.md` | agente MAF consome um MCP server externo |
|
|
45
47
|
| `ai-agents/mcp-server.md` | projeto expõe capacidades como MCP server |
|
|
48
|
+
| `ai-agents/agent-archetypes.md` | escolher a FORMA do agente na fase de plano (conversa com fases, one-shot em lote, visão + structured output) |
|
|
49
|
+
| `ai-agents/agent-spec.md` | o agente como DADO — um `AgentSpec` imutável por agente, catálogo e defaults |
|
|
50
|
+
| `ai-agents/conversational-agent-with-phases.md` | conversa com fases no turno — allow-list de tools, `ChatToolMode`, teto de tool calls |
|
|
51
|
+
| `ai-agents/guardrails.md` | regra proibida vira código — guard de tool, pertinência, argumento não confiável, fail-open |
|
|
52
|
+
| `ai-agents/testing-ai.md` | testar agente sem rede — `IChatClient` fake, harness de turno, golden de prompt |
|
|
53
|
+
| `ai-agents/evals-with-cache.md` | eval de modelo/prompt no CI **sem credencial e sem rede**: cache de respostas commitada, dataset versionado, o que entra na chave de cache, troca de modelo e regressão de prompt |
|
|
54
|
+
| `ai-agents/cost-and-budget.md` | custo em dinheiro — `UsageCostMiddleware`, tarifa por token, teto de orçamento |
|
|
55
|
+
| `ai-agents/llm-runtime-defaults.md` | knobs de runtime da chamada — timeout, retry, `strict` JSON schema, temperatura/reasoning |
|
|
56
|
+
| `ai-agents/durable-workflows-hitl.md` | workflow durável e human-in-the-loop — checkpoint, `RequestPort`, aprovação de tool |
|
|
57
|
+
| `ai-agents/vector-data-extensions.md` | abstração `Microsoft.Extensions.VectorData` e conectores (pgvector, ingestão) |
|
|
46
58
|
| `integration/mcp/mcp-tools.md` | integração MCP cross-domain (fora do escopo MAF) |
|
|
@@ -11,6 +11,8 @@ stacks:
|
|
|
11
11
|
# Infrastructure Standards
|
|
12
12
|
|
|
13
13
|
@.morph/framework/standards/infrastructure/docker/coolify-deploy.md
|
|
14
|
+
@.morph/framework/standards/infrastructure/docker/local-compose-isolation.md
|
|
15
|
+
@.morph/framework/standards/infrastructure/docker/local-compose-isolation.md
|
|
14
16
|
|
|
15
17
|
## Infrastructure Templates
|
|
16
18
|
|
|
@@ -18,7 +20,7 @@ stacks:
|
|
|
18
20
|
|----------|------|---------|
|
|
19
21
|
| .NET API Dockerfile | `.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile` | Multi-stage .NET |
|
|
20
22
|
| Next.js Dockerfile | `.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile` | Multi-stage Next.js |
|
|
21
|
-
| Docker Compose | `.morph/framework/templates/infrastructure/docker/docker-compose.template.yml` | Local dev env |
|
|
23
|
+
| Docker Compose | `.morph/framework/templates/infrastructure/docker/docker-compose.template.yml` | Local dev env — one isolated stack per worktree (ports from the block, no `container_name`) |
|
|
22
24
|
| Coolify Config | `.morph/framework/templates/infrastructure/docker/coolify.template.json` | Deployment config |
|
|
23
25
|
| .NET Build CI | `.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs` | CI workflow |
|
|
24
26
|
| Docker Push CI | `.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs` | Build & push |
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://polymorphism.com.br/morph-spec/schemas/ai-pin.schema.json",
|
|
4
|
+
"title": "morph-spec AI package pin",
|
|
5
|
+
"description": "Shape of framework/ai-pin.json — the single source of truth for the AI package versions the framework commits to. Written by a human after checking api.nuget.org; read by scripts/generate-ai-pin-props.js (Central Package Management + global.json) and by src/lib/standards/verified-against.js (age of ai-agents standards against pinnedAt). Every version is exact SemVer: a range here would restore floating versions in the generated .props, which is the defect this file exists to remove.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["pinnedAt", "source", "targetFramework", "sdk", "packages", "toolchain"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Relative path to this schema, so an editor validates the pin while it is edited."
|
|
13
|
+
},
|
|
14
|
+
"pinnedAt": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
17
|
+
"description": "ISO date on which a human checked these versions against the registry. It is the anchor of the ai-agents freshness window — NOT derived from `published`: `published` is a registry fact, `pinnedAt` is the framework's commitment. A date in the future is a pin error, never a fresh standard."
|
|
18
|
+
},
|
|
19
|
+
"source": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"pattern": "^https://",
|
|
22
|
+
"description": "The registry endpoint the versions were read from, so the check is reproducible."
|
|
23
|
+
},
|
|
24
|
+
"targetFramework": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"pattern": "^net\\d+\\.\\d+$",
|
|
27
|
+
"description": "TFM the kit compiles against — the TFM the pinned Microsoft.Agents.AI declares."
|
|
28
|
+
},
|
|
29
|
+
"sdk": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
32
|
+
"description": "SDK band written into the kit's global.json and read by actions/setup-dotnet."
|
|
33
|
+
},
|
|
34
|
+
"packages": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"description": "The AI packages the framework commits to. The SET is closed HERE, by `required` plus `maxProperties` — issue #92 pinned five, and issue #97 added the three Evaluation packages that make `verify` able to run evals from a committed response cache. `patternProperties` still carries the per-entry SHAPE, because JSON Schema cannot name a closed set of keys and repeat their shape without writing it once per key; test/framework/ai-pin.test.js reads THIS `required` list rather than a literal of its own, so the pin and its ruler can never name different sets.",
|
|
37
|
+
"required": [
|
|
38
|
+
"Microsoft.Agents.AI",
|
|
39
|
+
"Microsoft.Extensions.AI",
|
|
40
|
+
"OpenAI",
|
|
41
|
+
"Google.GenAI",
|
|
42
|
+
"ModelContextProtocol",
|
|
43
|
+
"Microsoft.Extensions.AI.Evaluation",
|
|
44
|
+
"Microsoft.Extensions.AI.Evaluation.Quality",
|
|
45
|
+
"Microsoft.Extensions.AI.Evaluation.Reporting"
|
|
46
|
+
],
|
|
47
|
+
"minProperties": 8,
|
|
48
|
+
"maxProperties": 8,
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"patternProperties": {
|
|
51
|
+
"^[A-Za-z0-9_.-]+$": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"required": ["version", "published", "role"],
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"properties": {
|
|
56
|
+
"version": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
59
|
+
"description": "Exact SemVer. No range, no wildcard, no floating version."
|
|
60
|
+
},
|
|
61
|
+
"published": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
64
|
+
"description": "ISO publication date of that exact version in the registry."
|
|
65
|
+
},
|
|
66
|
+
"role": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"pattern": "^[a-z0-9-]+$",
|
|
69
|
+
"description": "Human-readable label (maf-core, provider, mcp, ...). Documentation, not code semantics."
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"toolchain": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"description": "Build/test packages. They live in the SAME file because the generator writes a single Directory.Packages.props; splitting them would produce two version files and the very second-truth problem the pin removes.",
|
|
78
|
+
"minProperties": 3,
|
|
79
|
+
"maxProperties": 3,
|
|
80
|
+
"additionalProperties": false,
|
|
81
|
+
"patternProperties": {
|
|
82
|
+
"^[A-Za-z0-9_.-]+$": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"required": ["version"],
|
|
85
|
+
"additionalProperties": false,
|
|
86
|
+
"properties": {
|
|
87
|
+
"version": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
90
|
+
"description": "Exact SemVer, same rule as `packages`."
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "MORPH-SPEC .morph/config/config.json",
|
|
4
|
+
"description": "Describes ONLY project.tests[] — the plural declaration of the project's test suites. Everything else in config.json is deliberately left open (additionalProperties: true) so that configs already in the field never break on a key this schema has not heard of. The authority at runtime is validateTestNodes() in src/lib/tasks/test-nodes.js: a schema nothing calls is decoration.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"properties": {
|
|
8
|
+
"project": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": true,
|
|
11
|
+
"properties": {
|
|
12
|
+
"tests": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"description": "One entry per test suite. Absent (not empty) means the four-step fallback of resolveTestPlan() decides — every project created before this key existed keeps working. An empty array is an explicit declaration of 'no test suites'.",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"required": ["id", "command"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"id": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
23
|
+
"description": "Unique within the array. What `verify --filter` matches and the key of nodes.tests.runs[]. A duplicate id is a config error, not a silent tie."
|
|
24
|
+
},
|
|
25
|
+
"command": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"minLength": 1,
|
|
28
|
+
"description": "Literal command, executed by the shell from `cwd`."
|
|
29
|
+
},
|
|
30
|
+
"cwd": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"minLength": 1,
|
|
33
|
+
"default": ".",
|
|
34
|
+
"description": "Relative to the project root. A path that resolves outside the root is refused: it addresses another tree."
|
|
35
|
+
},
|
|
36
|
+
"runner": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["dotnet", "vitest", "jest", "node-test", "playwright", "none", "evals"],
|
|
39
|
+
"description": "Chooses the test-count extractor and whether a prebuild applies. Inferred from `command` when omitted; `none` means the count is UNKNOWN, never zero. `evals` is the ONLY value that is never inferred - it is a declaration that this suite reads a committed response cache, and it REQUIRES the `evals` block below."
|
|
40
|
+
},
|
|
41
|
+
"expectedCount": {
|
|
42
|
+
"type": "integer",
|
|
43
|
+
"minimum": 0,
|
|
44
|
+
"description": "Recorded baseline. Written ONLY by `morph-spec verify --record-baseline`, from a green run — never typed by hand."
|
|
45
|
+
},
|
|
46
|
+
"timeoutMs": {
|
|
47
|
+
"type": "integer",
|
|
48
|
+
"minimum": 1,
|
|
49
|
+
"description": "Ceiling for this node. Exists because the derived default (120s x chained projects) is shaped for `dotnet test` and is hostile to a Node suite that takes twelve minutes."
|
|
50
|
+
},
|
|
51
|
+
"build": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": { "type": "string", "minLength": 1 },
|
|
54
|
+
"description": "Test projects to compile before running (runner 'dotnet' only). Without it, `--no-build` in the command has no fresh binary behind it."
|
|
55
|
+
},
|
|
56
|
+
"e2e": {
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"default": false,
|
|
59
|
+
"description": "Requires the application stack to be up. Runs at feature scope only, never in the per-task loop."
|
|
60
|
+
},
|
|
61
|
+
"linuxImage": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"minLength": 1,
|
|
64
|
+
"description": "Overrides the container image used by `verify --linux` for this node."
|
|
65
|
+
},
|
|
66
|
+
"evals": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"additionalProperties": false,
|
|
69
|
+
"required": ["cachePath"],
|
|
70
|
+
"description": "Required by, and only valid for, runner evals. Declared on any other runner it would be a key the verifier silently ignores - the same fail-open shape as a typo in tests[].",
|
|
71
|
+
"properties": {
|
|
72
|
+
"cachePath": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"minLength": 1,
|
|
75
|
+
"description": "Root of the response-cache tree, relative to the project root, and COMMITTED. A path that resolves outside the root is refused, same rule as `cwd`. No default: a silent one would create a cache tree somewhere nobody declared."
|
|
76
|
+
},
|
|
77
|
+
"datasetPath": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"minLength": 1,
|
|
80
|
+
"description": "Root of the versioned dataset, laid out as {agent}/{tenant}/{scenario}.json. Defaults to `eval-datasets` beside cachePath."
|
|
81
|
+
},
|
|
82
|
+
"envFile": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"minLength": 1,
|
|
85
|
+
"description": "GITIGNORED file holding the provider credential, read ONLY by --refresh-cache. Same pattern as e2e.auth.envFile; a credential never travels in the committed config. Defaults to `.env.evals`."
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"evals": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"additionalProperties": true,
|
|
95
|
+
"description": "Project-level eval settings, distinct from the per-node block above: this one describes the PROJECT (which paths count as prompt or model), not one suite.",
|
|
96
|
+
"properties": {
|
|
97
|
+
"watch": {
|
|
98
|
+
"type": "array",
|
|
99
|
+
"items": { "type": "string", "minLength": 1 },
|
|
100
|
+
"description": "Globs that mean 'this is a prompt or a model'. Matched against what the feature changed (git diff plus untracked) to decide whether Gate 3 should pause for a missing eval. Absent means the project declares no such paths - reported as a reason of its own, distinct from 'declared and nothing was touched'."
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -107,7 +107,8 @@
|
|
|
107
107
|
"build": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
108
108
|
"tests": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
109
109
|
"validators": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
110
|
-
"e2e": { "type": "string", "enum": ["pass", "fail", "skip"], "description": "Docker bring-up + Playwright specs against the running stack. Absent on entries written before the node existed — which is why it is NOT in `required`." }
|
|
110
|
+
"e2e": { "type": "string", "enum": ["pass", "fail", "skip"], "description": "Docker bring-up + Playwright specs against the running stack. Absent on entries written before the node existed — which is why it is NOT in `required`." },
|
|
111
|
+
"evals": { "type": ["string", "null"], "enum": ["pass", "fail", "skip", null], "description": "Eval suites, pulled out of the tests rollup. Written by the SAME function that writes the singular stamp (nodeStatuses in verify.js): the two shapes must stay identical, or a stamp would omit what the history records. null means the project declares no eval node at all — distinct from skip, which means it declares one and this run did not execute it. Absent on entries written before the field existed, which is why it is NOT in required." }
|
|
111
112
|
},
|
|
112
113
|
"additionalProperties": false
|
|
113
114
|
},
|
|
@@ -134,7 +135,8 @@
|
|
|
134
135
|
"build": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
135
136
|
"tests": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
136
137
|
"validators": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
137
|
-
"e2e": { "type": "string", "enum": ["pass", "fail", "skip"], "description": "Docker bring-up + Playwright specs against the running stack. Absent on entries written before the node existed — which is why it is NOT in `required`." }
|
|
138
|
+
"e2e": { "type": "string", "enum": ["pass", "fail", "skip"], "description": "Docker bring-up + Playwright specs against the running stack. Absent on entries written before the node existed — which is why it is NOT in `required`." },
|
|
139
|
+
"evals": { "type": ["string", "null"], "enum": ["pass", "fail", "skip", null], "description": "Eval suites, pulled out of the tests rollup. Written by the SAME function that writes the singular stamp (nodeStatuses in verify.js): the two shapes must stay identical, or a stamp would omit what the history records. null means the project declares no eval node at all — distinct from skip, which means it declares one and this run did not execute it. Absent on entries written before the field existed, which is why it is NOT in required." }
|
|
138
140
|
},
|
|
139
141
|
"additionalProperties": false
|
|
140
142
|
},
|
|
@@ -149,13 +151,30 @@
|
|
|
149
151
|
},
|
|
150
152
|
"verification": {
|
|
151
153
|
"type": "object",
|
|
152
|
-
"description": "The single latest deterministic verify stamp (any scope), written ONLY by `morph-spec verify`. Distinct from `verifications`/`taskVerifications` above (per-scope history with attempts/nodes): this is overwritten by every verify call regardless of scope, carrying just enough to correlate a pass/fail with the exact commit it covers. Primary consumer: `morph-spec gate-check`, which feeds `status` to shouldPauseGate as `verificationStatus` — a red verify pauses the gate (risk-detector.js).
|
|
154
|
+
"description": "The single latest deterministic verify stamp (any scope), written ONLY by `morph-spec verify`. Distinct from `verifications`/`taskVerifications` above (per-scope history with attempts/nodes): this is overwritten by every verify call regardless of scope, carrying just enough to correlate a pass/fail with the exact commit it covers. Primary consumer: `morph-spec gate-check`, which feeds `status` to shouldPauseGate as `verificationStatus` — a red verify pauses the gate (risk-detector.js) — and `nodes.e2e`/`e2eReason` as `e2eStatus`/`e2eReason`, the pair that decides whether an e2e SKIP is a recorded dispensation or an absence of proof (a skip never reddens `status`, so the rollup alone is blind to it). `scope` is the third pause input: a dispensation only counts from a feature-scoped stamp (see the field below). The rest (sha/ts, and the derived `verificationStale`) is audit context.",
|
|
153
155
|
"required": ["status", "sha", "ts", "scope"],
|
|
154
156
|
"properties": {
|
|
155
157
|
"status": { "type": "string", "enum": ["pass", "fail"] },
|
|
158
|
+
"nodes": {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"description": "Per-node status of the verify that wrote this stamp. NOT in `required`: stamps written before the field existed carry only the rollup. Same values as `verifications.feature.nodes`, repeated here because gate-check reads the stamp and nothing else.",
|
|
161
|
+
"properties": {
|
|
162
|
+
"build": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
163
|
+
"tests": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
164
|
+
"validators": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
165
|
+
"e2e": { "type": "string", "enum": ["pass", "fail", "skip"] },
|
|
166
|
+
"evals": {
|
|
167
|
+
"type": ["string", "null"],
|
|
168
|
+
"enum": ["pass", "fail", "skip", null],
|
|
169
|
+
"description": "Status of the `runner: \"evals\"` entries of nodes.tests.runs[], pulled out of the rollup. The rollup cannot answer what Gate 3 asks: a red eval and a red unit test both read as tests:fail, and an eval SKIPPED at task scope leaves the rollup green. null means this project declares NO eval node - distinct from 'skip', which means it declares one and this run did not execute it; risk-detector.js pauses on the two for different reasons because they call for different actions. Absent on stamps written before this field existed."
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"additionalProperties": false
|
|
173
|
+
},
|
|
174
|
+
"e2eReason": { "type": ["string", "null"], "description": "The LITERAL reason the e2e node skipped (e2e-check.js's own wording), or null when it did not skip. risk-detector.js's classifyE2eSkip() reads it: 'project does not containerize' / 'does not touch runtime' are legitimate dispensations and pass; 'docker is not available' / 'requested' (--skip-e2e) / anything unclassifiable — null included — are an absence of proof and pause Gate 3. A skip recorded WITHOUT a reason is refused on purpose: a nameless dispensation is a silent no-op." },
|
|
156
175
|
"sha": { "type": ["string", "null"], "description": "HEAD commit SHA at verify time (git rev-parse HEAD); null when not in a git repo or no commits yet." },
|
|
157
176
|
"ts": { "type": "string", "format": "date-time", "description": "Server-side timestamp, same instant as the corresponding verifications/taskVerifications entry's updatedAt." },
|
|
158
|
-
"scope": { "type": "string", "description": "'feature', or 'task:<id>' for a task-scoped verify." }
|
|
177
|
+
"scope": { "type": "string", "description": "'feature', or 'task:<id>' for a task-scoped verify. READ BY THE GATE, not just audit: this stamp is singular and any verify overwrites it, so a 'task:<id>' value at ship time means the last verify was NOT the feature's. risk-detector.js therefore accepts an e2e dispensation ('project'/'scope' in e2eReason) only from scope === 'feature' — a `morph-spec verify {f} {task}` of a task whose group does not touch runtime stamps exactly the legitimate-sounding reason that would otherwise buy Gate 3 for the whole feature (the last task of a feature is typically group: docs). Absent/unknown falls on the safe side, like an unclassifiable reason." }
|
|
159
178
|
},
|
|
160
179
|
"additionalProperties": false
|
|
161
180
|
},
|
|
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.35.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|
|
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.35.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|