@polymorphism-tech/morph-spec 8.37.1 → 8.40.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 +37 -13
- package/README.md +2 -2
- package/bin/morph-spec.js +75 -49
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +6 -5
- package/framework/MORPH.md +25 -22
- package/framework/agents.json +96 -36
- package/framework/docs/CLI.md +54 -17
- package/framework/evals/rubrics/code-quality.md +5 -0
- package/framework/evals/rubrics/condensed.md +6 -2
- package/framework/evals/rubrics/contracts.md +20 -0
- package/framework/evals/rubrics/test-coverage.md +17 -6
- package/framework/hooks/README.md +9 -3
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
- package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
- package/framework/hooks/claude-code/core/validate-completion.js +16 -90
- package/framework/hooks/claude-code/core/worktree-context.js +10 -0
- package/framework/hooks/shared/command-tokens.js +96 -0
- package/framework/hooks/shared/git-exclude.js +44 -0
- package/framework/hooks/shared/git-paths.js +52 -0
- package/framework/hooks/shared/graph-freshness.js +380 -0
- package/framework/hooks/shared/graph-refresh.js +416 -0
- package/framework/hooks/shared/morph-cli.js +17 -5
- package/framework/hooks/shared/redact-secrets.js +55 -0
- package/framework/hooks/shared/telemetry-logger.js +25 -2
- package/framework/hooks/shared/transcript-usage.js +80 -0
- package/framework/mcp/registry.json +33 -2
- package/framework/rubrics/backend.json +2 -2
- package/framework/rubrics/default.json +2 -2
- package/framework/rubrics/frontend.json +2 -2
- package/framework/rules/csharp-standards.md +1 -0
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +10 -4
- package/framework/rules/nextjs-standards.md +1 -0
- package/framework/schemas/config.schema.json +41 -1
- package/framework/schemas/feature.schema.json +5 -1
- 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 +40 -11
- package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +44 -10
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +73 -15
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +51 -13
- 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 +39 -13
- package/framework/standards/STANDARDS.json +189 -14
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/ai-agents/testing-ai.md +2 -1
- package/framework/standards/architecture/code-comments.md +100 -0
- package/framework/standards/backend/dotnet/testing.md +22 -0
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/admin-app/data-table.md +562 -0
- package/framework/standards/frontend/admin-app/feedback.md +236 -0
- package/framework/standards/frontend/admin-app/forms.md +232 -0
- package/framework/standards/frontend/admin-app/page-structure.md +203 -0
- package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
- package/framework/standards/frontend/nextjs/components.md +37 -58
- package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
- package/framework/standards/frontend/nextjs/forms.md +31 -30
- package/framework/standards/frontend/nextjs/state-management.md +2 -1
- package/framework/standards/frontend/nextjs/testing.md +18 -0
- package/framework/standards/integration/mcp/mcp-tools.md +48 -117
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/context/design-system.md +3 -3
- package/framework/templates/docs/proposal.md +11 -0
- package/framework/templates/docs/spec.md +15 -0
- package/framework/templates/docs/ui-mockups.md +115 -98
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/feature/decisions.md +124 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/graph/affected.js +4 -3
- package/src/commands/graph/build.js +109 -18
- package/src/commands/graph/explain.js +8 -5
- package/src/commands/graph/status.js +14 -8
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/observability/cost.js +148 -18
- package/src/commands/project/doctor.js +85 -15
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +30 -0
- package/src/commands/project/worktree.js +73 -90
- package/src/commands/state/advance.js +18 -0
- package/src/commands/state/approve.js +15 -0
- package/src/commands/state/archive.js +62 -9
- package/src/commands/state/finish.js +62 -14
- package/src/commands/state/gate-check.js +8 -1
- package/src/commands/state/gate-decision.js +8 -4
- package/src/commands/state/report-check.js +90 -9
- package/src/commands/state/usecase.js +66 -0
- package/src/commands/tasks/dag.js +17 -2
- package/src/commands/validation/validate-feature.js +6 -1
- package/src/commands/validation/verify.js +20 -1
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +63 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/hooks-scanner.js +8 -1
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +4 -1
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/ai/comment-stripper.js +101 -4
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +47 -0
- package/src/lib/generators/handoff-generator.js +4 -18
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/git/default-branch.js +52 -0
- package/src/lib/git/tree-fingerprint.js +58 -0
- package/src/lib/git/tree-state.js +167 -0
- package/src/lib/graph/god-nodes.js +12 -3
- package/src/lib/graph/graph-health.js +48 -2
- package/src/lib/graph/graph-loader.js +99 -25
- package/src/lib/graph/graphifyignore.js +145 -28
- package/src/lib/graph/sanitize.js +117 -11
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/risk-detector.js +90 -0
- package/src/lib/specs/acervo-format.js +70 -0
- package/src/lib/specs/acervo-lifecycle.js +264 -0
- package/src/lib/specs/adr-index.js +169 -0
- package/src/lib/specs/revocations.js +156 -0
- package/src/lib/specs/screen-checklist.js +357 -0
- package/src/lib/specs/usecase-check.js +20 -16
- package/src/lib/specs/usecase-promoter.js +103 -38
- package/src/lib/specs/usecase-registry.js +7 -7
- package/src/lib/tasks/tasks-json-parser.js +2 -3
- package/src/lib/telemetry/metrics.js +386 -0
- package/src/lib/telemetry/telemetry-reader.js +161 -51
- package/src/lib/telemetry/verify-event.js +79 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +200 -0
- package/src/lib/validators/code-budget/comment-scanner.js +75 -0
- package/src/lib/validators/code-budget/index.js +9 -0
- package/src/lib/validators/reuse/reuse-validator.js +4 -2
- package/src/lib/validators/reuse/symbol-scanner.js +27 -22
- package/src/lib/validators/validation-runner.js +21 -5
- package/src/lib/validators/verify-runner.js +11 -0
- package/src/lib/worktree/finish-backup.js +739 -0
- package/src/lib/worktree/ignored-triage.js +15 -8
- package/src/lib/worktree/worktree-audit.js +5 -0
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +31 -22
- package/src/utils/agents-installer.js +338 -24
- package/src/utils/banner.js +1 -1
- package/src/utils/hooks-installer.js +75 -15
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
|
@@ -1,17 +1,134 @@
|
|
|
1
|
-
import { scanAll } from './scanner/index.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { scanAll } from './scanner/index.js';
|
|
2
|
+
import { classifyChange, watchTargets, isMorphDataReload } from './scanner/project-api.js';
|
|
3
|
+
import { createPortfolio } from './scanner/portfolio.js';
|
|
4
|
+
|
|
5
|
+
const VIRTUAL_MODULE_ID = 'virtual:morph-data';
|
|
6
|
+
const RESOLVED_VIRTUAL_ID = '\0' + VIRTUAL_MODULE_ID;
|
|
7
|
+
const DEBOUNCE_MS = 300;
|
|
8
|
+
const RESCAN_MS = 30_000;
|
|
9
|
+
const WARM_GAP_MS = 400;
|
|
10
|
+
const MAX_BODY = 64 * 1024;
|
|
11
|
+
|
|
12
|
+
function readBody(req) {
|
|
13
|
+
return new Promise((resolveBody) => {
|
|
14
|
+
let size = 0;
|
|
15
|
+
const chunks = [];
|
|
16
|
+
req.on('data', (c) => { size += c.length; if (size <= MAX_BODY) chunks.push(c); });
|
|
17
|
+
req.on('end', () => {
|
|
18
|
+
try { resolveBody(chunks.length ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : null); } catch { resolveBody(null); }
|
|
19
|
+
});
|
|
20
|
+
req.on('error', () => resolveBody(null));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Two data paths:
|
|
26
|
+
* - `virtual:morph-data` — the framework's knowledge (agents, phases, skills,
|
|
27
|
+
* standards, rules, hooks, docs). Static for the life of the server.
|
|
28
|
+
* - `/api/morph/*` — the portfolio (`/projects`) and each project's features
|
|
29
|
+
* and activity (`/p/:id/…`), read on demand and pushed as `morph:changed`
|
|
30
|
+
* over the HMR socket when a project's `.morph/` changes.
|
|
31
|
+
*/
|
|
32
|
+
export default function morphDataPlugin(frameworkDir, projectDir) {
|
|
33
|
+
return {
|
|
34
|
+
name: 'vite-plugin-morph-data',
|
|
35
|
+
resolveId(id) { if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_ID; },
|
|
36
|
+
load(id) {
|
|
37
|
+
if (id === RESOLVED_VIRTUAL_ID) {
|
|
38
|
+
const data = scanAll(frameworkDir);
|
|
39
|
+
return `export default ${JSON.stringify(data)}`;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
configureServer(server) {
|
|
43
|
+
const portfolio = createPortfolio({ launchDir: projectDir });
|
|
44
|
+
const push = (data) => server.ws.send({ type: 'custom', event: 'morph:changed', data });
|
|
45
|
+
|
|
46
|
+
// Vite reloads the whole page for any changed .html it watches, even when
|
|
47
|
+
// handleHotUpdate returns [] (vite 6: no module matched + .html = full
|
|
48
|
+
// reload). A project's .morph/ carries .html design sources, and an agent
|
|
49
|
+
// writing one reloaded the dashboard under the person reading it. The
|
|
50
|
+
// dashboard's own code never lives under .morph/.
|
|
51
|
+
const clientHot = server.environments?.client?.hot;
|
|
52
|
+
if (clientHot?.send) {
|
|
53
|
+
const send = clientHot.send.bind(clientHot);
|
|
54
|
+
clientHot.send = (payload, ...rest) => {
|
|
55
|
+
if (payload?.type === 'full-reload' && isMorphDataReload(payload.path)) return;
|
|
56
|
+
send(payload, ...rest);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const watched = new Set();
|
|
61
|
+
function watchNew() {
|
|
62
|
+
const fresh = portfolio.list().flatMap((p) => watchTargets(p.dir)).filter((p) => !watched.has(p));
|
|
63
|
+
for (const p of fresh) watched.add(p);
|
|
64
|
+
if (fresh.length) server.watcher.add(fresh);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Read the projects in the background, one at a time, so neither the first
|
|
68
|
+
// page nor the portfolio waits for a large project. Reading one blocks the
|
|
69
|
+
// event loop for seconds, so each step waits WARM_GAP_MS first: the reads
|
|
70
|
+
// a push (or an add) just caused are served before the next block.
|
|
71
|
+
let warming = false;
|
|
72
|
+
function warmSoon() {
|
|
73
|
+
if (warming) return;
|
|
74
|
+
warming = true;
|
|
75
|
+
const step = () => {
|
|
76
|
+
const warmed = portfolio.warmNext();
|
|
77
|
+
if (!warmed) { warming = false; return; }
|
|
78
|
+
push({ scope: 'projects' });
|
|
79
|
+
setTimeout(step, WARM_GAP_MS);
|
|
80
|
+
};
|
|
81
|
+
setTimeout(step, WARM_GAP_MS);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
server.middlewares.use('/api/morph', async (req, res) => {
|
|
85
|
+
const body = req.method === 'POST' ? await readBody(req) : null;
|
|
86
|
+
const { status, body: out } = portfolio.handle(req.method || 'GET', req.url || '/', body);
|
|
87
|
+
res.statusCode = status;
|
|
88
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
89
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
90
|
+
res.end(JSON.stringify(out));
|
|
91
|
+
// The portfolio's list changed: watch and read any project just added.
|
|
92
|
+
if (req.method !== 'GET' && status === 200) { watchNew(); push({ scope: 'projects' }); warmSoon(); }
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
watchNew();
|
|
96
|
+
warmSoon();
|
|
97
|
+
const rescan = setInterval(watchNew, RESCAN_MS);
|
|
98
|
+
rescan.unref?.();
|
|
99
|
+
|
|
100
|
+
const timers = new Map();
|
|
101
|
+
const changed = new Map(); // `${project}:${scope}` -> path -> watcher event, in the debounce window
|
|
102
|
+
const onFsEvent = (event, path) => {
|
|
103
|
+
const scope = classifyChange(path);
|
|
104
|
+
if (!scope) return;
|
|
105
|
+
const project = portfolio.ownerOf(path);
|
|
106
|
+
if (!project) return;
|
|
107
|
+
const key = `${project.id}:${scope}`;
|
|
108
|
+
if (!changed.has(key)) changed.set(key, new Map());
|
|
109
|
+
const seen = changed.get(key);
|
|
110
|
+
// A path added then changed in one window is still an add.
|
|
111
|
+
if (!seen.has(path) || seen.get(path) === 'change') seen.set(path, event);
|
|
112
|
+
clearTimeout(timers.get(key));
|
|
113
|
+
timers.set(key, setTimeout(() => {
|
|
114
|
+
timers.delete(key);
|
|
115
|
+
const paths = changed.get(key) || new Map();
|
|
116
|
+
changed.delete(key);
|
|
117
|
+
for (const [p, event] of paths) portfolio.invalidate(project, scope, p, event);
|
|
118
|
+
if (scope === 'features') watchNew();
|
|
119
|
+
push({ scope, project: project.id });
|
|
120
|
+
// A log append is tailed on read, so the portfolio can refresh now; a
|
|
121
|
+
// feature change is re-read in the background first, then pushed.
|
|
122
|
+
if (scope === 'activity') push({ scope: 'projects' });
|
|
123
|
+
else warmSoon();
|
|
124
|
+
}, DEBOUNCE_MS));
|
|
125
|
+
};
|
|
126
|
+
for (const evt of ['add', 'change', 'unlink', 'addDir', 'unlinkDir']) server.watcher.on(evt, (path) => onFsEvent(evt, path));
|
|
127
|
+
server.httpServer?.once('close', () => clearInterval(rescan));
|
|
128
|
+
},
|
|
129
|
+
// A changed `.morph/` file is data, not a module: never let it reach HMR.
|
|
130
|
+
handleHotUpdate(ctx) {
|
|
131
|
+
if (classifyChange(ctx.file)) return [];
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
* comment-stripper.js — apagar comentários SEM apagar o que está dentro de uma
|
|
3
3
|
* string, preservando comprimento e quebras de linha.
|
|
4
4
|
*
|
|
5
|
-
* Existe uma vez só, e serve a
|
|
5
|
+
* Existe uma vez só, e serve a consumidores que fariam a MESMA coisa de
|
|
6
6
|
* jeitos ligeiramente diferentes se cada um escrevesse o seu:
|
|
7
7
|
*
|
|
8
8
|
* - `code-audit.js` — não pode acusar `AgentThread` que só aparece num
|
|
9
9
|
* comentário ou numa mensagem de log;
|
|
10
10
|
* - `registry-audit.js` — precisa ler **JSONC**, a forma que o exemplar
|
|
11
11
|
* canônico `model-registry.v2.json` usa e que o leitor C# do kit aceita de
|
|
12
|
-
* propósito (`JsonCommentHandling.Skip`, `AllowTrailingCommas`)
|
|
12
|
+
* propósito (`JsonCommentHandling.Skip`, `AllowTrailingCommas`);
|
|
13
|
+
* - `dotnet-test-introspection.js` — lê o `global.json` por `jsoncToJson`;
|
|
14
|
+
* - `validators/code-budget/comment-scanner.js` — acha as linhas de
|
|
15
|
+
* comentário de `.cs`/`.ts`/`.js`, e é o único que liga a opção `js`.
|
|
13
16
|
*
|
|
14
17
|
* **Por que não um regex.** O exemplar tem `"http://localhost:11434/v1"` e uma
|
|
15
18
|
* `provenance` com URL. Um `replace(/\/\/.*$/gm, '')` decapitaria as duas —
|
|
@@ -36,13 +39,18 @@
|
|
|
36
39
|
* `○ não verificado`. O B1 reintroduzido pela correção do B1.
|
|
37
40
|
* @param {{ unterminated?: string|null }} [options.report]
|
|
38
41
|
* Se um objeto for passado, recebe `unterminated` = `'block-comment'`,
|
|
39
|
-
* `'string'`, `'verbatim-string'`, `'raw-string'` ou `null`. É o canal de
|
|
42
|
+
* `'string'`, `'verbatim-string'`, `'raw-string'`, `'template'` ou `null`. É o canal de
|
|
40
43
|
* diagnóstico que `jsoncToJson` usa para ficar tão estrito quanto o leitor
|
|
41
44
|
* de produção, sem que este lexer precise lançar (ele também varre `.cs`,
|
|
42
45
|
* onde um comentário aberto é problema do arquivo, não da varredura).
|
|
46
|
+
* @param {boolean} [options.js=false]
|
|
47
|
+
* Lê também template literal e regex literal de TS/JS. Opt-in porque só
|
|
48
|
+
* quem varre `.ts`/`.js` precisa: sem template, `https://${h}` vira
|
|
49
|
+
* comentário; sem regex, uma crase dentro de `/[`']/` abre um template que
|
|
50
|
+
* engole o resto do arquivo.
|
|
43
51
|
* @returns {string} texto do MESMO comprimento
|
|
44
52
|
*/
|
|
45
|
-
export function stripComments(text, { blankStrings = false, report = null } = {}) {
|
|
53
|
+
export function stripComments(text, { blankStrings = false, report = null, js = false } = {}) {
|
|
46
54
|
const out = text.split('');
|
|
47
55
|
const blank = (from, to) => {
|
|
48
56
|
for (let k = from; k < to && k < out.length; k++) {
|
|
@@ -73,6 +81,19 @@ export function stripComments(text, { blankStrings = false, report = null } = {}
|
|
|
73
81
|
continue;
|
|
74
82
|
}
|
|
75
83
|
|
|
84
|
+
if (js && c === '`') {
|
|
85
|
+
const end = templateEnd(text, i);
|
|
86
|
+
if (end === -1) flag('template');
|
|
87
|
+
const stop = end === -1 ? text.length : end;
|
|
88
|
+
if (blankStrings) blank(i + 1, end === -1 ? stop : stop - 1);
|
|
89
|
+
i = stop;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (js && c === '/' && next !== '>' && regexMayStart(out, i)) {
|
|
93
|
+
const end = regexEnd(text, i);
|
|
94
|
+
if (end !== -1) { i = end; continue; }
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
// Raw string literal do C#: """ ... """ — delimitadores de 3 aspas.
|
|
77
98
|
if (c === '"' && next === '"' && text[i + 2] === '"') {
|
|
78
99
|
const end = text.indexOf('"""', i + 3);
|
|
@@ -137,6 +158,82 @@ export function stripComments(text, { blankStrings = false, report = null } = {}
|
|
|
137
158
|
return out.join('');
|
|
138
159
|
}
|
|
139
160
|
|
|
161
|
+
/** Índice logo após a crase que fecha o template aberto em `start`, ou -1. */
|
|
162
|
+
function templateEnd(text, start) {
|
|
163
|
+
let j = start + 1;
|
|
164
|
+
while (j < text.length) {
|
|
165
|
+
if (text[j] === '\\') { j += 2; continue; }
|
|
166
|
+
if (text[j] === '`') return j + 1;
|
|
167
|
+
if (text[j] === '$' && text[j + 1] === '{') { j = holeEnd(text, j + 2); continue; }
|
|
168
|
+
j += 1;
|
|
169
|
+
}
|
|
170
|
+
return -1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const REGEX_AFTER_WORD = new Set([
|
|
174
|
+
'return', 'typeof', 'case', 'do', 'else', 'in', 'of', 'new', 'delete', 'void',
|
|
175
|
+
'throw', 'instanceof', 'yield', 'await',
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Uma `/` abre regex, e não divisão, quando o que vem antes não é um valor: o
|
|
180
|
+
* mesmo critério de contexto dos tokenizers de JS. `out` já tem os comentários
|
|
181
|
+
* apagados, então o caractere anterior é código. Sem `<`: em TSX, `</b>` é tag.
|
|
182
|
+
*/
|
|
183
|
+
function regexMayStart(out, i) {
|
|
184
|
+
let k = i - 1;
|
|
185
|
+
while (k >= 0 && /\s/.test(out[k])) k--;
|
|
186
|
+
if (k < 0) return true;
|
|
187
|
+
if ('(,=:[!&|?{};+-*%>~^'.includes(out[k])) return true;
|
|
188
|
+
if (!/[A-Za-z_$]/.test(out[k])) return false;
|
|
189
|
+
let w = k;
|
|
190
|
+
while (w > 0 && /[\w$]/.test(out[w - 1])) w--;
|
|
191
|
+
return REGEX_AFTER_WORD.has(out.slice(w, k + 1).join(''));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Índice logo após a `/` (e as flags) que fecha a regex aberta em `start`, ou -1 se a linha acaba antes. */
|
|
195
|
+
function regexEnd(text, start) {
|
|
196
|
+
let inClass = false;
|
|
197
|
+
for (let j = start + 1; j < text.length; j++) {
|
|
198
|
+
const c = text[j];
|
|
199
|
+
if (c === '\n') return -1;
|
|
200
|
+
if (c === '\\') { j++; continue; }
|
|
201
|
+
if (c === '[') inClass = true;
|
|
202
|
+
else if (c === ']') inClass = false;
|
|
203
|
+
else if (c === '/' && !inClass) {
|
|
204
|
+
let k = j + 1;
|
|
205
|
+
while (k < text.length && /[a-z]/i.test(text[k])) k++;
|
|
206
|
+
return k;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return -1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Índice logo após o `}` que fecha um `${`: o código do buraco aninha chaves, strings e templates. */
|
|
213
|
+
function holeEnd(text, start) {
|
|
214
|
+
let depth = 1;
|
|
215
|
+
let j = start;
|
|
216
|
+
while (j < text.length) {
|
|
217
|
+
const c = text[j];
|
|
218
|
+
if (c === '`') {
|
|
219
|
+
const end = templateEnd(text, j);
|
|
220
|
+
if (end === -1) return text.length;
|
|
221
|
+
j = end;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (c === '"' || c === "'") {
|
|
225
|
+
j += 1;
|
|
226
|
+
while (j < text.length && text[j] !== c && text[j] !== '\n') j += text[j] === '\\' ? 2 : 1;
|
|
227
|
+
j += 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (c === '{') depth += 1;
|
|
231
|
+
else if (c === '}' && --depth === 0) return j + 1;
|
|
232
|
+
j += 1;
|
|
233
|
+
}
|
|
234
|
+
return text.length;
|
|
235
|
+
}
|
|
236
|
+
|
|
140
237
|
/**
|
|
141
238
|
* Converte JSONC em JSON: apaga comentários e vírgulas sobrando antes de `}`/`]`.
|
|
142
239
|
*
|
|
@@ -15,13 +15,12 @@ import { homedir } from 'os';
|
|
|
15
15
|
* Detect Claude Code configuration for a project.
|
|
16
16
|
*
|
|
17
17
|
* @param {string} targetPath - Root path of the project
|
|
18
|
-
* @returns {{ plugins: string[], mcpServers: Array<{name: string, config: Object}>,
|
|
18
|
+
* @returns {{ plugins: string[], mcpServers: Array<{name: string, config: Object}>, customSkills: string[], customCommands: string[], hooks: string[] }}
|
|
19
19
|
*/
|
|
20
20
|
export function detectClaudeConfig(targetPath) {
|
|
21
21
|
const result = {
|
|
22
22
|
plugins: [],
|
|
23
23
|
mcpServers: [],
|
|
24
|
-
superpowers: false,
|
|
25
24
|
customSkills: [],
|
|
26
25
|
customCommands: [],
|
|
27
26
|
hooks: [],
|
|
@@ -86,7 +85,6 @@ export function detectClaudeConfig(targetPath) {
|
|
|
86
85
|
const pluginsData = JSON.parse(readFileSync(pluginsPath, 'utf8'));
|
|
87
86
|
const plugins = pluginsData.plugins || {};
|
|
88
87
|
result.plugins = Object.keys(plugins);
|
|
89
|
-
result.superpowers = 'superpowers@claude-plugins-official' in plugins;
|
|
90
88
|
} catch { /* invalid JSON */ }
|
|
91
89
|
}
|
|
92
90
|
|
|
@@ -166,7 +164,7 @@ export function mcpAvailabilityByPhase(mcpServers, phaseMatrix = {}) {
|
|
|
166
164
|
/**
|
|
167
165
|
* Format a human-readable one-liner summary of detected config.
|
|
168
166
|
*
|
|
169
|
-
* @param {{ plugins: string[], mcpServers: Array<{name: string}
|
|
167
|
+
* @param {{ plugins: string[], mcpServers: Array<{name: string}> }} config
|
|
170
168
|
* @returns {string}
|
|
171
169
|
*/
|
|
172
170
|
export function formatConfigSummary(config) {
|
|
@@ -177,8 +175,5 @@ export function formatConfigSummary(config) {
|
|
|
177
175
|
if (config.plugins.length > 0) {
|
|
178
176
|
parts.push(`Plugins: ${config.plugins.length}`);
|
|
179
177
|
}
|
|
180
|
-
if (config.superpowers) {
|
|
181
|
-
parts.push('superpowers: yes');
|
|
182
|
-
}
|
|
183
178
|
return parts.join(' | ') || 'No Claude config detected';
|
|
184
179
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview Project Detector
|
|
3
3
|
*
|
|
4
4
|
* Scans a project directory for stack, architecture, UI library, and integrations.
|
|
5
|
-
* Used by
|
|
5
|
+
* Used by `morph-spec init`, `mcp setup` and the test-node fallback (test-nodes.js).
|
|
6
6
|
*
|
|
7
7
|
* @module detectors/index
|
|
8
8
|
*/
|
|
@@ -106,6 +106,10 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
106
106
|
if (content.includes('Hangfire')) {
|
|
107
107
|
if (!structure.integrations.includes('hangfire')) structure.integrations.push('hangfire');
|
|
108
108
|
}
|
|
109
|
+
// Same heuristic as the package.json side (`pg`/`postgres` → neon).
|
|
110
|
+
if (content.includes('Npgsql')) {
|
|
111
|
+
if (!structure.integrations.includes('neon')) structure.integrations.push('neon');
|
|
112
|
+
}
|
|
109
113
|
} catch { /* ignore */ }
|
|
110
114
|
}
|
|
111
115
|
|
|
@@ -93,6 +93,12 @@ const MUTATION_SECTION_RE = /^##\s+Muta[çc][õo]es\s*$/m;
|
|
|
93
93
|
*/
|
|
94
94
|
const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* A seção em que o avaliador confere o checklist de tela do mockup contra a
|
|
98
|
+
* tela entregue (`morph-review` §4). Mesma disciplina: exige `##`.
|
|
99
|
+
*/
|
|
100
|
+
const SCREEN_CHECKLIST_SECTION_RE = /^##\s+Checklist\s+de\s+tela\s*$/gim;
|
|
101
|
+
|
|
96
102
|
/**
|
|
97
103
|
* As chaves reconhecidas de `**Proof-Scores:**`, uma por rubrica de prova.
|
|
98
104
|
* Chave fora desta lista é IGNORADA (não vira score, não vira erro): o relatório
|
|
@@ -287,6 +293,25 @@ export function evaluatorProofState(cwd, featureName) {
|
|
|
287
293
|
};
|
|
288
294
|
}
|
|
289
295
|
|
|
296
|
+
/**
|
|
297
|
+
* A seção `## Checklist de tela` do relatório, com o corpo da ÚLTIMA ocorrência
|
|
298
|
+
* — append-only outra vez: numa reavaliação a conferência nova vai para o fim.
|
|
299
|
+
* O corpo vai cru para `checklistCoverage` (`src/lib/specs/screen-checklist.js`),
|
|
300
|
+
* dono único do parser de linha do checklist.
|
|
301
|
+
*
|
|
302
|
+
* @returns {{ section: boolean, body: string }} arquivo ausente ou sem a seção:
|
|
303
|
+
* `{ section: false, body: '' }`. Nunca lança.
|
|
304
|
+
*/
|
|
305
|
+
export function evaluatorScreenChecklist(cwd, featureName) {
|
|
306
|
+
const text = readReport(cwd, featureName);
|
|
307
|
+
const matches = text === null ? [] : [...text.matchAll(SCREEN_CHECKLIST_SECTION_RE)];
|
|
308
|
+
if (matches.length === 0) return { section: false, body: '' };
|
|
309
|
+
const last = matches[matches.length - 1];
|
|
310
|
+
const rest = text.slice(last.index + last[0].length);
|
|
311
|
+
const next = rest.search(/^#{1,2}\s/m);
|
|
312
|
+
return { section: true, body: next === -1 ? rest : rest.slice(0, next) };
|
|
313
|
+
}
|
|
314
|
+
|
|
290
315
|
/**
|
|
291
316
|
* O menor Proof-Score das provas de EVIDÊNCIA — o que impõe o teto em
|
|
292
317
|
* `test-coverage`.
|
|
@@ -324,3 +349,25 @@ export function mediaProofScore(proofScores) {
|
|
|
324
349
|
const value = proofScores?.media;
|
|
325
350
|
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
326
351
|
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Os dois vereditos do relatório lidos de um TEXTO já em mãos — o score
|
|
355
|
+
* composto vigente e os Proof-Scores vigentes, pela mesma regra append-only
|
|
356
|
+
* (o último token vale) dos leitores por `cwd` acima.
|
|
357
|
+
*
|
|
358
|
+
* Existe para quem tem o relatório fora do caminho canônico — o dashboard lê
|
|
359
|
+
* features arquivadas em `.morph/archive/{f}/4-review/` —, sem que ele
|
|
360
|
+
* precise de uma segunda cópia dos regexes.
|
|
361
|
+
*
|
|
362
|
+
* @param {string} text
|
|
363
|
+
* @returns {{ score: number|undefined, proofScores: Record<string, number>|undefined }}
|
|
364
|
+
*/
|
|
365
|
+
export function evaluatorVerdictFromText(text) {
|
|
366
|
+
if (typeof text !== 'string') return { score: undefined, proofScores: undefined };
|
|
367
|
+
const scores = compositeScoresIn(text);
|
|
368
|
+
const lines = [...text.matchAll(PROOF_SCORES_RE)];
|
|
369
|
+
return {
|
|
370
|
+
score: scores.length === 0 ? undefined : scores[scores.length - 1],
|
|
371
|
+
proofScores: lines.length === 0 ? undefined : parseProofScores(lines[lines.length - 1][1]),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* degrades that section, never the document.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { existsSync,
|
|
20
|
-
import { join
|
|
21
|
-
import {
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import { ensureCommonExclude } from '../../../framework/hooks/shared/git-exclude.js';
|
|
22
22
|
import { derivePhaseForFeature, getPendingGates } from '../../../framework/hooks/shared/state-reader.js';
|
|
23
23
|
|
|
24
24
|
const PHASE_DIRS = {
|
|
@@ -236,21 +236,7 @@ const HANDOFF_EXCLUDE_PATTERN = '/.morph/**/handoff.md';
|
|
|
236
236
|
* untracked is noise, never data loss.
|
|
237
237
|
*/
|
|
238
238
|
function ensureHandoffExcluded(cwd) {
|
|
239
|
-
|
|
240
|
-
const common = execSync('git rev-parse --git-common-dir', { cwd, stdio: 'pipe' }).toString().trim();
|
|
241
|
-
if (!common) return;
|
|
242
|
-
const infoDir = join(resolve(cwd, common), 'info');
|
|
243
|
-
const excludePath = join(infoDir, 'exclude');
|
|
244
|
-
const current = existsSync(excludePath) ? readFileSync(excludePath, 'utf8') : '';
|
|
245
|
-
const present = new Set(current.split(/\r?\n/).map((l) => l.trim()));
|
|
246
|
-
if (present.has(HANDOFF_EXCLUDE_PATTERN)) return;
|
|
247
|
-
mkdirSync(infoDir, { recursive: true });
|
|
248
|
-
appendFileSync(excludePath, [
|
|
249
|
-
current && !current.endsWith('\n') ? '\n' : '',
|
|
250
|
-
present.has(HANDOFF_EXCLUDE_MARKER) ? '' : `${HANDOFF_EXCLUDE_MARKER}\n`,
|
|
251
|
-
`${HANDOFF_EXCLUDE_PATTERN}\n`,
|
|
252
|
-
].join(''));
|
|
253
|
-
} catch { /* fail-open */ }
|
|
239
|
+
ensureCommonExclude(cwd, [HANDOFF_EXCLUDE_PATTERN], HANDOFF_EXCLUDE_MARKER);
|
|
254
240
|
}
|
|
255
241
|
|
|
256
242
|
/**
|