@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
|
@@ -7,12 +7,20 @@
|
|
|
7
7
|
* @module graph-build
|
|
8
8
|
*/
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
-
import {
|
|
11
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
10
|
+
import { readFileSync, existsSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
12
11
|
import { join } from 'node:path';
|
|
13
|
-
import {
|
|
12
|
+
import { GRAPHIFY_PACKAGE, GRAPHIFY_VERSION } from '../../lib/graph/graphify-runtime.js';
|
|
14
13
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
15
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
buildGraphifyignore, writeGraphifyignore, describeOutdatedIgnore,
|
|
16
|
+
} from '../../lib/graph/graphifyignore.js';
|
|
17
|
+
import {
|
|
18
|
+
acquireRefreshLock, isPrimaryTree, readRefreshLock, releaseRefreshLock, resolveGraphify, runGraphify,
|
|
19
|
+
} from '../../../framework/hooks/shared/graph-refresh.js';
|
|
20
|
+
import { isSecondaryWorktree } from '../../../framework/hooks/shared/git-paths.js';
|
|
21
|
+
import {
|
|
22
|
+
TEMP_IGNORE_MARKER, ignoreRulesHash, ignoreTextOf, isTemporaryIgnore, readHead, recordRefreshRun,
|
|
23
|
+
} from '../../../framework/hooks/shared/graph-freshness.js';
|
|
16
24
|
import { resolveGraphPath } from '../../lib/graph/graph-loader.js';
|
|
17
25
|
|
|
18
26
|
function readProjectTags(projectPath) {
|
|
@@ -25,6 +33,44 @@ function readProjectTags(projectPath) {
|
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
|
|
36
|
+
/**
|
|
37
|
+
* O `.graphifyignore` com que o graphify vai rodar.
|
|
38
|
+
*
|
|
39
|
+
* Na raiz: escrito ou migrado (writeGraphifyignore). Numa worktree o arquivo é
|
|
40
|
+
* do checkout e nunca é reescrito — seria uma mudança na branch da feature.
|
|
41
|
+
* Ausente ali (a raiz ainda não o versionou), as regras — as da raiz
|
|
42
|
+
* (`ignoreTextOf`), ou as geradas — entram só durante a rodada e saem depois
|
|
43
|
+
* (`cleanup`): um `?? .graphifyignore` deixado na worktree faz o `finish` e
|
|
44
|
+
* o `worktree remove --if-clean` recusarem com worktree-dirty (medido), e pôr
|
|
45
|
+
* o arquivo no exclude do common dir esconderia também o da raiz. O arquivo leva
|
|
46
|
+
* `TEMP_IGNORE_MARKER` na 1ª linha: se este processo morrer antes do
|
|
47
|
+
* `cleanup`, a sobra é reconhecida pelo conteúdo — a próxima rodada a reusa e o
|
|
48
|
+
* `finish` a apaga (`dropLeftoverIgnore`). O graphify não herda o da raiz: o
|
|
49
|
+
* ignore dele para na raiz de VCS mais próxima, que numa worktree é ela mesma
|
|
50
|
+
* (detect.py `_load_graphifyignore`).
|
|
51
|
+
*
|
|
52
|
+
* @param {string} projectPath
|
|
53
|
+
* @param {string[]} tags
|
|
54
|
+
* @returns {{written: boolean, migrated?: boolean, outdated?: object, temporary?: boolean, path: string, cleanup: () => void}}
|
|
55
|
+
*/
|
|
56
|
+
function prepareIgnore(projectPath, tags) {
|
|
57
|
+
const none = () => {};
|
|
58
|
+
if (!isSecondaryWorktree(projectPath)) return { ...writeGraphifyignore(projectPath, tags), cleanup: none };
|
|
59
|
+
const path = join(projectPath, '.graphifyignore');
|
|
60
|
+
if (existsSync(path) && !isTemporaryIgnore(readFileSync(path, 'utf-8'))) {
|
|
61
|
+
const preview = writeGraphifyignore(projectPath, tags, { fx: { writeFile: none } });
|
|
62
|
+
return { written: false, path, ...(preview.outdated ? { outdated: preview.outdated } : {}), cleanup: none };
|
|
63
|
+
}
|
|
64
|
+
const content = ignoreTextOf(projectPath) ?? buildGraphifyignore(tags);
|
|
65
|
+
writeFileSync(path, `${TEMP_IGNORE_MARKER}\n${content.replace(/^/, '')}`, 'utf-8');
|
|
66
|
+
return {
|
|
67
|
+
written: false,
|
|
68
|
+
temporary: true,
|
|
69
|
+
path,
|
|
70
|
+
cleanup: () => { try { unlinkSync(path); } catch { /* já saiu */ } },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
28
74
|
/**
|
|
29
75
|
* @param {{path?: string, json?: boolean, refresh?: boolean}} [options]
|
|
30
76
|
*/
|
|
@@ -32,8 +78,10 @@ export async function graphBuildCommand(options = {}) {
|
|
|
32
78
|
const projectPath = options.path || process.cwd();
|
|
33
79
|
const refresh = options.refresh === true;
|
|
34
80
|
|
|
35
|
-
|
|
36
|
-
|
|
81
|
+
// O mesmo resolvedor do refresh em background: no win32 um shim `.cmd` só
|
|
82
|
+
// é achado pelo shell, e os dois caminhos precisam ver o mesmo binário.
|
|
83
|
+
if (!resolveGraphify(process.env)) {
|
|
84
|
+
const hint = `graphify não está no PATH. Instale com: uv tool install ${GRAPHIFY_PACKAGE}==${GRAPHIFY_VERSION}`;
|
|
37
85
|
const payload = { ok: false, reason: 'graphify-unavailable', hint };
|
|
38
86
|
if (options.json) {
|
|
39
87
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -47,7 +95,33 @@ export async function graphBuildCommand(options = {}) {
|
|
|
47
95
|
|
|
48
96
|
// O ignore precisa existir ANTES da extração, senão o grafo nasce com o
|
|
49
97
|
// ruído que a ADR-004 existe para tirar.
|
|
50
|
-
|
|
98
|
+
const ignore = prepareIgnore(projectPath, readProjectTags(projectPath));
|
|
99
|
+
if (ignore.outdated && !options.json) {
|
|
100
|
+
console.error(chalk.yellow(`\n⚠ ${describeOutdatedIgnore(ignore.outdated)}`));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Na árvore principal o refresh em background (framework/hooks/shared/
|
|
104
|
+
// graph-refresh.js) escreve o mesmo graph.json: dois graphify ao mesmo tempo
|
|
105
|
+
// disputam o arquivo e todos os núcleos. Numa worktree ninguém mais escreve
|
|
106
|
+
// o graph.json dela — o background só toca a raiz —, e esperar o lock da
|
|
107
|
+
// raiz ali prometeria "o grafo chega ao HEAD" sobre um refresh que nunca a
|
|
108
|
+
// alcança. Sem git não há lock, e o comando segue sozinho.
|
|
109
|
+
const primary = isPrimaryTree(projectPath);
|
|
110
|
+
const lock = primary ? acquireRefreshLock(projectPath) : null;
|
|
111
|
+
const running = primary && !lock ? readRefreshLock(projectPath) : null;
|
|
112
|
+
if (running?.live) {
|
|
113
|
+
ignore.cleanup();
|
|
114
|
+
const since = new Date(running.since).toLocaleTimeString();
|
|
115
|
+
if (options.json) {
|
|
116
|
+
console.log(JSON.stringify({
|
|
117
|
+
ok: true, running: true, since: running.since, pid: running.pid, mode: refresh ? 'refresh' : 'build',
|
|
118
|
+
}, null, 2));
|
|
119
|
+
} else {
|
|
120
|
+
console.log(chalk.yellow(`\n⟳ Refresh em background já em andamento desde ${since} (pid ${running.pid}).`));
|
|
121
|
+
console.log(chalk.gray(' O grafo chega ao HEAD quando ele terminar — confira com: morph-spec graph status\n'));
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
51
125
|
|
|
52
126
|
const args = refresh
|
|
53
127
|
? ['update', projectPath]
|
|
@@ -56,19 +130,33 @@ export async function graphBuildCommand(options = {}) {
|
|
|
56
130
|
// Capturamos o output em vez de herdar o terminal porque precisamos
|
|
57
131
|
// inspecioná-lo: o graphify sai 1 tanto quando quebra quanto quando não há
|
|
58
132
|
// código para indexar, e essas duas coisas não podem virar a mesma mensagem.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
133
|
+
// HEAD e regras lidos ANTES: são o que o graphify leu.
|
|
134
|
+
const head = readHead(projectPath);
|
|
135
|
+
const ignoreHash = ignoreRulesHash(projectPath);
|
|
136
|
+
let run;
|
|
137
|
+
let empty = false;
|
|
138
|
+
try {
|
|
139
|
+
run = runGraphify(projectPath, args, { timeout: null });
|
|
140
|
+
// Projeto sem código é o estado normal logo depois de um `init`, não uma
|
|
141
|
+
// falha: o graphify diz "found 0 code" / "graph is empty", sai 1 e não
|
|
142
|
+
// escreve graph.json. Tratar isso como erro faria todo init greenfield
|
|
143
|
+
// terminar em vermelho.
|
|
144
|
+
empty = run.status !== 0 && (/found 0 code/i.test(run.output) || /graph is empty/i.test(run.output));
|
|
145
|
+
// O registro é o que torna "untouched" em dia: sem ele, um refresh que não
|
|
146
|
+
// mudou a topologia deixa o carimbo no commit antigo e o grafo parece
|
|
147
|
+
// velho para sempre (framework/hooks/shared/graph-freshness.js).
|
|
148
|
+
if (!empty) {
|
|
149
|
+
recordRefreshRun(projectPath, {
|
|
150
|
+
head, exit: run.status ?? null, ignoreHash, unavailable: Boolean(run.unavailable),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
releaseRefreshLock(lock);
|
|
155
|
+
ignore.cleanup();
|
|
156
|
+
}
|
|
64
157
|
|
|
65
|
-
const
|
|
158
|
+
const { output } = run;
|
|
66
159
|
const ok = run.status === 0;
|
|
67
|
-
// Projeto sem código é o estado normal logo depois de um `init`, não uma
|
|
68
|
-
// falha: o graphify diz "found 0 code" / "graph is empty", sai 1 e não
|
|
69
|
-
// escreve graph.json. Tratar isso como erro faria todo init greenfield
|
|
70
|
-
// terminar em vermelho.
|
|
71
|
-
const empty = !ok && (/found 0 code/i.test(output) || /graph is empty/i.test(output));
|
|
72
160
|
|
|
73
161
|
if (options.json) {
|
|
74
162
|
console.log(JSON.stringify({
|
|
@@ -77,6 +165,9 @@ export async function graphBuildCommand(options = {}) {
|
|
|
77
165
|
mode: refresh ? 'refresh' : 'build',
|
|
78
166
|
path: resolveGraphPath(projectPath),
|
|
79
167
|
exitCode: run.status,
|
|
168
|
+
...(ignore.migrated ? { ignoreMigrated: true } : {}),
|
|
169
|
+
...(ignore.outdated ? { ignoreOutdated: ignore.outdated } : {}),
|
|
170
|
+
...(ignore.temporary ? { ignoreTemporary: true } : {}),
|
|
80
171
|
}, null, 2));
|
|
81
172
|
} else {
|
|
82
173
|
if (!empty && output.trim()) process.stdout.write(`${output.trimEnd()}\n`);
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* @module graph-explain
|
|
12
12
|
*/
|
|
13
13
|
import chalk from 'chalk';
|
|
14
|
-
import { loadGraph, getFreshness } from '../../lib/graph/graph-loader.js';
|
|
15
|
-
import { buildIndex, resolveLabel, neighborhood } from '../../lib/graph/sanitize.js';
|
|
14
|
+
import { loadGraph, getFreshness, describeStale } from '../../lib/graph/graph-loader.js';
|
|
15
|
+
import { buildIndex, resolveLabel, neighborhood, isTestPath } from '../../lib/graph/sanitize.js';
|
|
16
16
|
|
|
17
17
|
const FALLBACK_HINT = 'Sem resposta do grafo, caia para grep/Glob.';
|
|
18
18
|
|
|
@@ -62,6 +62,7 @@ export async function graphExplainCommand(symbol, options = {}) {
|
|
|
62
62
|
label: node.label,
|
|
63
63
|
source_file: node.source_file,
|
|
64
64
|
source_location: node.source_location ?? '',
|
|
65
|
+
test: isTestPath(node.source_file),
|
|
65
66
|
},
|
|
66
67
|
incoming,
|
|
67
68
|
outgoing,
|
|
@@ -74,16 +75,18 @@ export async function graphExplainCommand(symbol, options = {}) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const nodeLoc = node.source_location ? `:${node.source_location}` : '';
|
|
77
|
-
|
|
78
|
+
const testTag = payload.node.test ? chalk.gray(' [teste]') : '';
|
|
79
|
+
console.log(chalk.green(`\n${node.label}`) + chalk.gray(` ${node.source_file}${nodeLoc}`) + testTag);
|
|
78
80
|
if (freshness.state === 'stale') {
|
|
79
|
-
console.log(chalk.yellow(
|
|
81
|
+
console.log(chalk.yellow(` ⚠ grafo stale — ${describeStale(projectPath, freshness)}`));
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
console.log(chalk.bold('\n quem depende dele:'));
|
|
83
85
|
for (const e of incoming) {
|
|
84
86
|
const loc = e.source_location ? `:${e.source_location}` : '';
|
|
85
87
|
const via = e.via === 'direct' ? '' : chalk.gray(` [${e.via}]`);
|
|
86
|
-
|
|
88
|
+
const test = e.test ? chalk.gray(' [teste]') : '';
|
|
89
|
+
console.log(` ${chalk.cyan(e.relation.padEnd(14))} ${e.source_file}${loc}${via}${test}`);
|
|
87
90
|
}
|
|
88
91
|
if (incoming.length === 0) console.log(chalk.gray(' (nenhum)'));
|
|
89
92
|
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* @module graph-status
|
|
9
9
|
*/
|
|
10
10
|
import chalk from 'chalk';
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
loadGraph, getFreshness, describeStale, refreshState, staleCause,
|
|
13
|
+
} from '../../lib/graph/graph-loader.js';
|
|
14
|
+
import { buildIndex, classifyLabel, isTestPath } from '../../lib/graph/sanitize.js';
|
|
13
15
|
import { GRAPHIFY_VERSION, detectGraphifyVersion } from '../../lib/graph/graphify-runtime.js';
|
|
14
16
|
|
|
15
17
|
function summarizeSanitize(index) {
|
|
@@ -52,13 +54,16 @@ export async function graphStatusCommand(options = {}) {
|
|
|
52
54
|
|
|
53
55
|
const { graph } = loaded;
|
|
54
56
|
const freshness = getFreshness(projectPath, graph);
|
|
55
|
-
const
|
|
57
|
+
const index = buildIndex(graph);
|
|
58
|
+
const sanitize = { ...summarizeSanitize(index), recoveredDispatch: index.recovered.length };
|
|
56
59
|
const payload = {
|
|
57
60
|
available: true,
|
|
58
61
|
path: loaded.path,
|
|
59
62
|
nodes: graph.nodes.length,
|
|
63
|
+
testNodes: graph.nodes.filter((n) => isTestPath(n.source_file)).length,
|
|
60
64
|
links: graph.links.length,
|
|
61
65
|
freshness,
|
|
66
|
+
refresh: refreshState(projectPath),
|
|
62
67
|
version,
|
|
63
68
|
sanitize,
|
|
64
69
|
};
|
|
@@ -72,10 +77,8 @@ export async function graphStatusCommand(options = {}) {
|
|
|
72
77
|
console.log(chalk.gray(` ${loaded.path}`));
|
|
73
78
|
|
|
74
79
|
if (freshness.state === 'stale') {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.log(chalk.yellow(` ⚠ stale — construído em ${freshness.builtAtCommit.slice(0, 8)}, HEAD é ${freshness.headCommit.slice(0, 8)}`));
|
|
78
|
-
console.log(chalk.gray(' Atualize com: morph-spec graph refresh'));
|
|
80
|
+
console.log(chalk.yellow(` ⚠ stale — ${staleCause(freshness)}`));
|
|
81
|
+
console.log(chalk.gray(` ${describeStale(projectPath, freshness)}`));
|
|
79
82
|
} else if (freshness.state === 'fresh') {
|
|
80
83
|
console.log(chalk.gray(` fresco (${freshness.headCommit.slice(0, 8)})`));
|
|
81
84
|
} else {
|
|
@@ -88,6 +91,9 @@ export async function graphStatusCommand(options = {}) {
|
|
|
88
91
|
console.log(chalk.yellow(` ⚠ graphify ${installed} instalado; ${GRAPHIFY_VERSION} é a versão pinada e medida`));
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
console.log(chalk.gray(` saneamento: ${sanitize.coalescible} coalescível · ${sanitize.external} externo · ${sanitize.ambiguous} ambíguo`));
|
|
94
|
+
console.log(chalk.gray(` saneamento: ${sanitize.coalescible} coalescível · ${sanitize.external} externo · ${sanitize.ambiguous} ambíguo · ${sanitize.recoveredDispatch} dispatch recuperado`));
|
|
95
|
+
if (payload.testNodes) {
|
|
96
|
+
console.log(chalk.gray(` ${payload.testNodes} nós de teste (dublês, fixtures, helpers) — achados pelo explain, fora dos god nodes`));
|
|
97
|
+
}
|
|
92
98
|
console.log('');
|
|
93
99
|
}
|
|
@@ -64,21 +64,49 @@ export async function detectStack(targetPath) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Detect the project's integrations (`neon`, `stripe`...), same sources and
|
|
69
|
+
* order as detectStack. The detectors never fold them into the stack string, so
|
|
70
|
+
* without this an MCP scoped to an integration (neon, asaas) is never offered.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} targetPath
|
|
73
|
+
* @returns {Promise<string[]>}
|
|
74
|
+
*/
|
|
75
|
+
export async function detectIntegrations(targetPath) {
|
|
76
|
+
try {
|
|
77
|
+
const configPath = join(targetPath, '.morph', 'config', 'config.json');
|
|
78
|
+
if (existsSync(configPath)) {
|
|
79
|
+
const integrations = JSON.parse(readFileSync(configPath, 'utf-8')).project?.integrations;
|
|
80
|
+
if (Array.isArray(integrations)) return integrations;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Fall through to filesystem detection
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const results = await detectProject(targetPath, { conversation: false, generateStandards: false });
|
|
88
|
+
return results.structure?.integrations ?? [];
|
|
89
|
+
} catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
67
94
|
export async function mcpSetupCommand(name, options) {
|
|
68
95
|
const targetPath = process.cwd();
|
|
69
96
|
|
|
70
97
|
try {
|
|
71
98
|
// Detect current environment
|
|
72
99
|
const stack = await detectStack(targetPath);
|
|
100
|
+
const integrations = await detectIntegrations(targetPath);
|
|
73
101
|
const claudeConfig = await detectClaudeConfig(targetPath);
|
|
74
102
|
const existingMcps = claudeConfig.mcpServers || [];
|
|
75
103
|
|
|
76
104
|
if (options.list) {
|
|
77
|
-
return showStatusList(targetPath, stack, existingMcps);
|
|
105
|
+
return showStatusList(targetPath, stack, existingMcps, integrations);
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
if (options.auto) {
|
|
81
|
-
return autoInstall(targetPath, stack, existingMcps);
|
|
109
|
+
return autoInstall(targetPath, stack, existingMcps, integrations);
|
|
82
110
|
}
|
|
83
111
|
|
|
84
112
|
if (name) {
|
|
@@ -86,7 +114,7 @@ export async function mcpSetupCommand(name, options) {
|
|
|
86
114
|
}
|
|
87
115
|
|
|
88
116
|
// Interactive wizard
|
|
89
|
-
return interactiveWizard(targetPath, stack, existingMcps);
|
|
117
|
+
return interactiveWizard(targetPath, stack, existingMcps, integrations);
|
|
90
118
|
} catch (err) {
|
|
91
119
|
console.error(chalk.red(`Error: ${err.message}`));
|
|
92
120
|
process.exit(1);
|
|
@@ -96,8 +124,8 @@ export async function mcpSetupCommand(name, options) {
|
|
|
96
124
|
/**
|
|
97
125
|
* Show MCP status list (--list)
|
|
98
126
|
*/
|
|
99
|
-
async function showStatusList(targetPath, stack, existingMcps) {
|
|
100
|
-
const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps);
|
|
127
|
+
async function showStatusList(targetPath, stack, existingMcps, integrations) {
|
|
128
|
+
const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { integrations });
|
|
101
129
|
const statusRows = formatMcpStatusTable(orchestration, {});
|
|
102
130
|
|
|
103
131
|
console.log(chalk.cyan('\n MCP Server Status\n'));
|
|
@@ -130,8 +158,8 @@ async function showStatusList(targetPath, stack, existingMcps) {
|
|
|
130
158
|
/**
|
|
131
159
|
* Auto-install credential-free MCPs (--auto)
|
|
132
160
|
*/
|
|
133
|
-
async function autoInstall(targetPath, stack, existingMcps) {
|
|
134
|
-
const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { autoOnly: true });
|
|
161
|
+
async function autoInstall(targetPath, stack, existingMcps, integrations) {
|
|
162
|
+
const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { autoOnly: true, integrations });
|
|
135
163
|
const autoNames = Object.keys(orchestration.autoInstallable);
|
|
136
164
|
|
|
137
165
|
if (autoNames.length === 0) {
|
|
@@ -391,8 +419,8 @@ async function setupSpecificMcp(targetPath, name, stack, existingMcps) {
|
|
|
391
419
|
/**
|
|
392
420
|
* Interactive wizard showing all MCPs
|
|
393
421
|
*/
|
|
394
|
-
async function interactiveWizard(targetPath, stack, existingMcps) {
|
|
395
|
-
const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps);
|
|
422
|
+
async function interactiveWizard(targetPath, stack, existingMcps, integrations) {
|
|
423
|
+
const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { integrations });
|
|
396
424
|
|
|
397
425
|
console.log(chalk.cyan('\n MCP Setup Wizard'));
|
|
398
426
|
console.log(chalk.gray(` Stack: ${stack}`));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `morph-spec cost [feature] [--by task|phase] [--all-trees] [--json]`
|
|
2
|
+
* `morph-spec cost [feature] [--by task|phase] [--all-trees] [--metrics [--top n]] [--json]`
|
|
3
3
|
*
|
|
4
4
|
* A cost-facing view over the same harness event stream `telemetry` reads
|
|
5
5
|
* (.morph/logs/events.jsonl): how much wall-clock time was spent inside
|
|
@@ -16,11 +16,14 @@
|
|
|
16
16
|
* `sessionUsage` (when present) is the exception: real tokens, observed
|
|
17
17
|
* directly, not a proxy — see aggregateCost's docstring for the contract.
|
|
18
18
|
*
|
|
19
|
-
* `--all-trees`
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* `--all-trees` widens the source from "just the current tree" to every git
|
|
20
|
+
* worktree that has its own events.jsonl plus the logs `finish` saved before
|
|
21
|
+
* removing a worktree, deduplicated by eventId.
|
|
22
|
+
*
|
|
23
|
+
* `--metrics` measures the loop instead: turns, tool calls by orchestrator and
|
|
24
|
+
* sub-agent, time per command class, identical reruns and why, rereads, how
|
|
25
|
+
* many feature-scope verifies each feature ran, git calls per task and whether
|
|
26
|
+
* the reuse validator ran in root and worktree (see lib/telemetry/metrics.js).
|
|
24
27
|
*
|
|
25
28
|
* Read-only, always exits 0 on a normal (even empty) run — fail-open, an
|
|
26
29
|
* empty stream is an answer, not an error. Only `--by` with an unknown value
|
|
@@ -28,7 +31,8 @@
|
|
|
28
31
|
*/
|
|
29
32
|
|
|
30
33
|
import chalk from 'chalk';
|
|
31
|
-
import { loadTelemetry, loadTelemetryAllTrees, aggregateCost } from '../../lib/telemetry/telemetry-reader.js';
|
|
34
|
+
import { loadTelemetry, loadTelemetryAllTrees, aggregateCost, dedupeEvents } from '../../lib/telemetry/telemetry-reader.js';
|
|
35
|
+
import { computeMetrics } from '../../lib/telemetry/metrics.js';
|
|
32
36
|
|
|
33
37
|
const BYTES_CAVEAT =
|
|
34
38
|
'Bytes re-ingeridos são um PROXY determinístico de custo (o que as ferramentas leram do disco) — NÃO são tokens reais. Hooks não enxergam o uso real de tokens do modelo.';
|
|
@@ -87,17 +91,30 @@ function printPhases(phases) {
|
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
function tokenLine(u) {
|
|
95
|
+
return ` input=${u.inputTokens} output=${u.outputTokens} `
|
|
96
|
+
+ `cacheRead=${u.cacheReadTokens} cacheCreation=${u.cacheCreationTokens}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
90
99
|
/**
|
|
91
|
-
* Real (hook-observed) token counts
|
|
92
|
-
*
|
|
93
|
-
* No-op when absent (aggregateCost returns `null`, never a zeroed object).
|
|
100
|
+
* Real (hook-observed) token counts — the non-proxy sibling of the bytes
|
|
101
|
+
* above: the main sessions and, separately, the sub-agents.
|
|
94
102
|
*/
|
|
95
|
-
function printSessionUsage(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
+ chalk.
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
function printSessionUsage(summary) {
|
|
104
|
+
const { sessionUsage, agentUsage, sessionUsageLegacySamples } = summary;
|
|
105
|
+
if (sessionUsage) {
|
|
106
|
+
console.log('\n' + chalk.white.bold('Tokens reais (sessões observadas)')
|
|
107
|
+
+ chalk.gray(` · ${sessionUsage.sessions} sessão(ões), ${sessionUsage.samples} amostra(s)`));
|
|
108
|
+
console.log(tokenLine(sessionUsage));
|
|
109
|
+
}
|
|
110
|
+
if (agentUsage) {
|
|
111
|
+
console.log('\n' + chalk.white.bold('Tokens reais (sub-agents)')
|
|
112
|
+
+ chalk.gray(` · ${agentUsage.agents} agente(s), ${agentUsage.samples} amostra(s)`));
|
|
113
|
+
console.log(tokenLine(agentUsage));
|
|
114
|
+
}
|
|
115
|
+
if (sessionUsageLegacySamples > 0) {
|
|
116
|
+
console.log(chalk.gray(` ${sessionUsageLegacySamples} amostra(s) SessionUsage antigas (sem delta) fora da soma: eram o total acumulado, repetido a cada Stop.`));
|
|
117
|
+
}
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
function printSummary(summary) {
|
|
@@ -112,7 +129,115 @@ function printSummary(summary) {
|
|
|
112
129
|
for (const t of summary.tools) console.log(` ${t.name.padEnd(16)} ${t.count}`);
|
|
113
130
|
}
|
|
114
131
|
|
|
115
|
-
printSessionUsage(summary
|
|
132
|
+
printSessionUsage(summary);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function pct(r) {
|
|
136
|
+
return r === null || r === undefined ? '-' : `${Math.round(r * 1000) / 10}%`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function split(counts) {
|
|
140
|
+
return `${counts.total} (orq ${counts.orchestrator} · sub ${counts.subagent}${counts.unknown ? ` · sem agente ${counts.unknown}` : ''})`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function printMetricsBody(m, { by }) {
|
|
144
|
+
const legacyShare = m.window.events ? m.coverage.legacy / m.window.events : 0;
|
|
145
|
+
console.log(chalk.gray(`janela: ${m.window.from ?? '-'} → ${m.window.to ?? '-'} · ${m.window.events} eventos`
|
|
146
|
+
+ ` · ${pct(1 - legacyShare)} no esquema v2`));
|
|
147
|
+
if (m.coverage.legacy) {
|
|
148
|
+
console.log(chalk.gray(' eventos v1 não têm agente, exit code, duração nem janela de Read: entram como "sem agente" e rerun "unknown-*".'));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('\n' + chalk.white.bold('Turnos e chamadas'));
|
|
152
|
+
console.log(` turnos: ${m.turns ? split(m.turns) : 'desconhecido (nenhum PostToolBatch na janela)'}`
|
|
153
|
+
+ (m.turnsPerTaskMedian !== null ? chalk.gray(` · mediana por task ${m.turnsPerTaskMedian}`) : ''));
|
|
154
|
+
console.log(` chamadas de ferramenta: ${split(m.toolCalls)}`);
|
|
155
|
+
const perTask = m.perTaskByFeature;
|
|
156
|
+
if (perTask.toolCalls !== null) {
|
|
157
|
+
console.log(` por task (total da feature ÷ tasks dela, mediana): ${perTask.toolCalls} chamadas`
|
|
158
|
+
+ (perTask.turns !== null ? ` · ${perTask.turns} turnos` : '') + ` · ${perTask.git} git status+diff`
|
|
159
|
+
+ chalk.gray(` · as janelas TaskStart→TaskEnd têm ${pct(m.taskWindowCoverage)} das chamadas`));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log('\n' + chalk.white.bold('Tempo por classe de comando'));
|
|
163
|
+
for (const [klass, t] of Object.entries(m.timeByClass).sort((a, b) => b[1].measuredMs - a[1].measuredMs)) {
|
|
164
|
+
const unmeasured = t.unmeasured ? chalk.gray(` (${t.unmeasured} sem duração)`) : '';
|
|
165
|
+
console.log(` ${klass.padEnd(12)} ${fmtDuration(t.measuredMs / 1000).padStart(8)} ${t.runs} execução(ões)${unmeasured}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log('\n' + chalk.white.bold('Rerun do mesmo comando em ≤10 min (mesma feature; o filtro de saída não conta)'));
|
|
169
|
+
console.log(` total: ${pct(m.reruns.rate)} repetidas (${m.reruns.reruns}/${m.reruns.runs}) · ${pct(m.reruns.burstRate)} em rajada — testes, builds e verifies`);
|
|
170
|
+
for (const [kind, r] of Object.entries(m.rerunsByKind).sort((a, b) => b[1].runs - a[1].runs)) {
|
|
171
|
+
console.log(` ${kind.padEnd(22)} ${pct(r.rate).padStart(6)} repetidas ${pct(r.burstRate).padStart(6)} em rajada (${r.runs} execuções)`);
|
|
172
|
+
}
|
|
173
|
+
const classes = Object.entries(m.rerunClasses);
|
|
174
|
+
if (classes.length) console.log(chalk.gray(` por quê: ${classes.map(([k, n]) => `${k} ${n}`).join(' · ')}`));
|
|
175
|
+
|
|
176
|
+
console.log('\n' + chalk.white.bold('Releitura de arquivo (mesma janela, mesma feature)'));
|
|
177
|
+
console.log(` ${pct(m.rereads.rate)} dos bytes lidos (${fmtBytes(m.rereads.redundantBytes)} de ${fmtBytes(m.rereads.bytes)}, ${m.rereads.reads} leituras)`);
|
|
178
|
+
if (m.rereads.windowed < m.rereads.reads) {
|
|
179
|
+
console.log(chalk.gray(` ${m.rereads.reads - m.rereads.windowed} leitura(s) sem janela gravada (v1) contam o arquivo inteiro: a taxa é um teto`));
|
|
180
|
+
}
|
|
181
|
+
for (const f of m.topRereads) {
|
|
182
|
+
console.log(` ${fmtBytes(f.redundantBytes).padStart(9)} ${f.reads}× ${f.file}${chalk.gray(` [${f.feature ?? '-'}]`)}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log('\n' + chalk.white.bold('verify, git e reuso'));
|
|
186
|
+
const perFeature = Object.entries(m.verify.byFeature).sort((a, b) => b[1].feature - a[1].feature);
|
|
187
|
+
console.log(` verify de feature por feature: mediana ${m.verify.featureScopeMedian ?? '-'}`
|
|
188
|
+
+ (perFeature.length ? chalk.gray(` (${perFeature.slice(0, 5).map(([f, v]) => `${f} ${v.feature}`).join(', ')})`) : ''));
|
|
189
|
+
const repeats = Object.entries(m.verify.unchangedRepeats);
|
|
190
|
+
if (repeats.length) {
|
|
191
|
+
console.log(` verify de feature sobre a mesma árvore do anterior: ${repeats.reduce((n, [, v]) => n + v, 0)}`
|
|
192
|
+
+ chalk.gray(` (${repeats.map(([f, n]) => `${f} ${n}`).join(', ')})`));
|
|
193
|
+
}
|
|
194
|
+
if (m.dotnetFullSuite.runs) {
|
|
195
|
+
const phases = Object.entries(m.dotnetFullSuite.byPhase).map(([p, v]) => `${p} ${v.withBuild}/${v.runs}`).join(' · ');
|
|
196
|
+
console.log(` suíte .NET inteira que recompila (sem --no-build): ${m.dotnetFullSuite.withBuild}/${m.dotnetFullSuite.runs}`
|
|
197
|
+
+ chalk.gray(` (${phases})`));
|
|
198
|
+
}
|
|
199
|
+
console.log(` git status / diff por task: mediana ${m.gitPerTaskMedian.status ?? '-'} / ${m.gitPerTaskMedian.diff ?? '-'}`
|
|
200
|
+
+ chalk.gray(` · total ${m.git.status} / ${m.git.diff}`));
|
|
201
|
+
const trees = Object.entries(m.reuse.byTree);
|
|
202
|
+
console.log(` reuse-validator: ${trees.length
|
|
203
|
+
? trees.map(([tree, s]) => `${tree} rodou ${s.ran}, pulou ${s.skipped}`).join(' · ')
|
|
204
|
+
: 'nenhum evento Verify na janela'}`);
|
|
205
|
+
for (const [reason, n] of Object.entries(m.reuse.skipReasons)) console.log(chalk.gray(` pulou ${n}×: ${reason}`));
|
|
206
|
+
|
|
207
|
+
if (by === 'task') {
|
|
208
|
+
for (const t of m.tasks) {
|
|
209
|
+
const status = t.open ? chalk.yellow('[em aberto]') : (t.status || '-');
|
|
210
|
+
console.log('\n' + chalk.white.bold(`Task ${t.task}`) + chalk.gray(` · ${t.feature ?? '(sem feature)'}`) + ` · ${status}`);
|
|
211
|
+
console.log(` turnos ${t.turns ? split(t.turns) : '-'} · chamadas ${split(t.toolCalls)}`);
|
|
212
|
+
const time = Object.entries(t.timeByClass).map(([k, v]) => `${k} ${fmtDuration(v.measuredMs / 1000)}`).join(' · ');
|
|
213
|
+
if (time) console.log(` tempo: ${time}`);
|
|
214
|
+
console.log(` rerun ${pct(t.reruns.rate)} (${t.reruns.reruns}/${t.reruns.runs}) · releitura ${pct(t.rereads.rate)} · git status/diff ${t.git.status}/${t.git.diff}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* `--metrics`: the loop measurements over the current tree (and, with
|
|
221
|
+
* `--all-trees`, every tree plus the finish backups, deduplicated).
|
|
222
|
+
*/
|
|
223
|
+
function printMetrics(feature, options, cwd) {
|
|
224
|
+
const events = options.allTrees
|
|
225
|
+
? dedupeEvents(loadTelemetryAllTrees(cwd).flatMap((t) => t.events))
|
|
226
|
+
: loadTelemetry(cwd);
|
|
227
|
+
const metrics = computeMetrics(events, { feature, top: options.top });
|
|
228
|
+
|
|
229
|
+
if (options.json) {
|
|
230
|
+
console.log(JSON.stringify(metrics));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (metrics.window.events === 0) {
|
|
234
|
+
const scope = feature ? ` para a feature "${feature}"` : '';
|
|
235
|
+
console.log(chalk.gray(`Telemetria vazia${scope} — nada a medir.`));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const scope = feature ? chalk.cyan(` · feature ${feature}`) : '';
|
|
239
|
+
console.log(chalk.white.bold(`Medições do loop${options.allTrees ? ' — todas as árvores + finish-backup' : ''}`) + scope);
|
|
240
|
+
printMetricsBody(metrics, options);
|
|
116
241
|
}
|
|
117
242
|
|
|
118
243
|
/**
|
|
@@ -132,7 +257,7 @@ function printAllTrees(feature, options, cwd) {
|
|
|
132
257
|
path,
|
|
133
258
|
...aggregateCost(events, { feature }),
|
|
134
259
|
}));
|
|
135
|
-
const combined = aggregateCost(treesData.flatMap((t) => t.events), { feature });
|
|
260
|
+
const combined = aggregateCost(dedupeEvents(treesData.flatMap((t) => t.events)), { feature });
|
|
136
261
|
|
|
137
262
|
if (options.json) {
|
|
138
263
|
console.log(JSON.stringify({ allTrees: true, trees: perTree, combined }));
|
|
@@ -183,6 +308,11 @@ export function costCommand(feature, options = {}) {
|
|
|
183
308
|
|
|
184
309
|
const cwd = options.cwd || process.cwd();
|
|
185
310
|
|
|
311
|
+
if (options.metrics) {
|
|
312
|
+
printMetrics(feature, options, cwd);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
186
316
|
if (options.allTrees) {
|
|
187
317
|
// Same fail-open + Windows-flush discipline as the single-tree path
|
|
188
318
|
// below — no forced process.exit(), --json included.
|