@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
package/src/utils/banner.js
CHANGED
|
@@ -117,7 +117,7 @@ export function printInstallBanner({ version, nodeVersion, results = [] }) {
|
|
|
117
117
|
sep("Next · new project"),
|
|
118
118
|
` ${chalk.cyan("1.")} ${DIM("cd")} ${chalk.white("your-project")} ${DIM("(git repo)")}`,
|
|
119
119
|
` ${chalk.cyan("2.")} Run ${chalk.bold.white("morph-spec init")}`,
|
|
120
|
-
` ${chalk.cyan("3.")} Open it in Claude Code and run ${chalk.bold.white("/morph-
|
|
120
|
+
` ${chalk.cyan("3.")} Open it in Claude Code and run ${chalk.bold.white("/morph-proposal <feature>")}`,
|
|
121
121
|
"",
|
|
122
122
|
sep("Next · project already on morph-spec"),
|
|
123
123
|
` ${chalk.cyan("1.")} Run ${chalk.bold.white("morph-spec update")} ${DIM("— after a CLI upgrade or a project.stack change: full stack-aware resync")}`,
|
|
@@ -35,8 +35,25 @@ import { realEffects } from '../lib/installers/effects.js';
|
|
|
35
35
|
* (junctions, port block, primary-root landing) and its removal routed
|
|
36
36
|
* through the safe `worktree remove --if-clean` — and appends
|
|
37
37
|
* block-raw-worktree-add to the PreToolUse:Bash chain.
|
|
38
|
+
*
|
|
39
|
+
* 3.9.0 binds telemetry-log to PostToolUseFailure (a Bash command that exits
|
|
40
|
+
* non-zero fires only that event, so red runs used to vanish from the log),
|
|
41
|
+
* PostToolBatch (once per model turn that used tools) and SubagentStop (the
|
|
42
|
+
* sub-agent's own token usage). The three only record, so they run with
|
|
43
|
+
* `async: true`: a node process per model turn never holds the turn up.
|
|
44
|
+
*
|
|
45
|
+
* 3.10.0 appends graph-refresh-kick to the all-tools PostToolUse chain: after a
|
|
46
|
+
* Bash or PowerShell command that runs git/gh/morph-spec it launches the
|
|
47
|
+
* detached refresh of the ROOT's codebase graph
|
|
48
|
+
* (framework/hooks/shared/graph-refresh.js). `update` re-runs
|
|
49
|
+
* this installer, so installed projects get the refresh without re-running
|
|
50
|
+
* `setup-infra` — the step that never reached them.
|
|
51
|
+
*
|
|
52
|
+
* 3.11.0 writes every `timeout` in seconds, the unit Claude Code reads: the
|
|
53
|
+
* values were milliseconds, so a hung hook held the session for hours (120000
|
|
54
|
+
* on WorktreeCreate is 33 h) instead of the seconds they meant.
|
|
38
55
|
*/
|
|
39
|
-
const HOOKS_VERSION = '3.
|
|
56
|
+
const HOOKS_VERSION = '3.11.0';
|
|
40
57
|
|
|
41
58
|
/** Marker for old dispatch.js (v1) */
|
|
42
59
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -119,7 +136,7 @@ const MORPH_HOOKS = [
|
|
|
119
136
|
hooks: [{
|
|
120
137
|
type: 'command',
|
|
121
138
|
command: 'node framework/hooks/claude-code/core/worktree-context.js',
|
|
122
|
-
timeout:
|
|
139
|
+
timeout: 5
|
|
123
140
|
}]
|
|
124
141
|
},
|
|
125
142
|
|
|
@@ -173,7 +190,7 @@ const MORPH_HOOKS = [
|
|
|
173
190
|
hooks: [{
|
|
174
191
|
type: 'command',
|
|
175
192
|
command: 'node framework/hooks/claude-code/core/worktree-create.js',
|
|
176
|
-
timeout:
|
|
193
|
+
timeout: 120
|
|
177
194
|
}]
|
|
178
195
|
},
|
|
179
196
|
|
|
@@ -188,7 +205,7 @@ const MORPH_HOOKS = [
|
|
|
188
205
|
hooks: [{
|
|
189
206
|
type: 'command',
|
|
190
207
|
command: 'node framework/hooks/claude-code/core/worktree-remove.js',
|
|
191
|
-
timeout:
|
|
208
|
+
timeout: 60
|
|
192
209
|
}]
|
|
193
210
|
},
|
|
194
211
|
|
|
@@ -227,24 +244,66 @@ const MORPH_HOOKS = [
|
|
|
227
244
|
type: 'command',
|
|
228
245
|
command: 'node framework/hooks/claude-code/core/chain.js',
|
|
229
246
|
args: ['state-sync', 'gate-guard'],
|
|
230
|
-
timeout:
|
|
247
|
+
timeout: 6
|
|
231
248
|
}]
|
|
232
249
|
},
|
|
233
250
|
|
|
234
|
-
// === PostToolUse: All — telemetry capture (silent) + loop detection
|
|
235
|
-
// telemetry-log is the single generic vantage point of an
|
|
236
|
-
// every tool call (skill, sub-agent dispatch, knowledge-base
|
|
237
|
-
// .morph/logs/events.jsonl. Silent and fail-open — never blocks,
|
|
238
|
-
// injects. `morph-spec telemetry` reads the stream back.
|
|
239
|
-
//
|
|
251
|
+
// === PostToolUse: All — telemetry capture (silent) + loop detection +
|
|
252
|
+
// graph refresh kick. telemetry-log is the single generic vantage point of an
|
|
253
|
+
// NLH: it appends every tool call (skill, sub-agent dispatch, knowledge-base
|
|
254
|
+
// read, …) to .morph/logs/events.jsonl. Silent and fail-open — never blocks,
|
|
255
|
+
// never injects. `morph-spec telemetry` reads the stream back.
|
|
256
|
+
// graph-refresh-kick only spawns a DETACHED worker (never waits on graphify),
|
|
257
|
+
// so it rides this chain instead of costing a process of its own per Bash
|
|
258
|
+
// call. Timeout stays the sum of the first two links (sequential worst case);
|
|
259
|
+
// the kick returns in milliseconds. ===
|
|
240
260
|
{
|
|
241
261
|
event: 'PostToolUse',
|
|
242
262
|
matcher: null,
|
|
243
263
|
hooks: [{
|
|
244
264
|
type: 'command',
|
|
245
265
|
command: 'node framework/hooks/claude-code/core/chain.js',
|
|
246
|
-
args: ['telemetry-log', 'loop-detect'],
|
|
247
|
-
timeout:
|
|
266
|
+
args: ['telemetry-log', 'loop-detect', 'graph-refresh-kick'],
|
|
267
|
+
timeout: 10
|
|
268
|
+
}]
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// === PostToolUseFailure: All — telemetry of the failed call (exit code,
|
|
272
|
+
// duration). loop-detect stays out: its warning is shaped for PostToolUse.
|
|
273
|
+
{
|
|
274
|
+
event: 'PostToolUseFailure',
|
|
275
|
+
matcher: null,
|
|
276
|
+
hooks: [{
|
|
277
|
+
type: 'command',
|
|
278
|
+
command: 'node framework/hooks/claude-code/core/telemetry-log.js',
|
|
279
|
+
timeout: 5,
|
|
280
|
+
async: true
|
|
281
|
+
}]
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// === PostToolBatch — one event per model turn that used tools. The event
|
|
285
|
+
// takes no matcher. ===
|
|
286
|
+
{
|
|
287
|
+
event: 'PostToolBatch',
|
|
288
|
+
matcher: null,
|
|
289
|
+
hooks: [{
|
|
290
|
+
type: 'command',
|
|
291
|
+
command: 'node framework/hooks/claude-code/core/telemetry-log.js',
|
|
292
|
+
timeout: 5,
|
|
293
|
+
async: true
|
|
294
|
+
}]
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
// === SubagentStop — the sub-agent's token usage, read from its own
|
|
298
|
+
// transcript (the session transcript does not include it). ===
|
|
299
|
+
{
|
|
300
|
+
event: 'SubagentStop',
|
|
301
|
+
matcher: null,
|
|
302
|
+
hooks: [{
|
|
303
|
+
type: 'command',
|
|
304
|
+
command: 'node framework/hooks/claude-code/core/telemetry-log.js',
|
|
305
|
+
timeout: 10,
|
|
306
|
+
async: true
|
|
248
307
|
}]
|
|
249
308
|
},
|
|
250
309
|
|
|
@@ -258,7 +317,7 @@ const MORPH_HOOKS = [
|
|
|
258
317
|
hooks: [{
|
|
259
318
|
type: 'command',
|
|
260
319
|
command: 'node framework/hooks/claude-code/core/telemetry-log.js',
|
|
261
|
-
timeout:
|
|
320
|
+
timeout: 5
|
|
262
321
|
}]
|
|
263
322
|
},
|
|
264
323
|
|
|
@@ -338,7 +397,8 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
338
397
|
return {
|
|
339
398
|
type: h.type,
|
|
340
399
|
command,
|
|
341
|
-
...(h.timeout !== undefined ? { timeout: h.timeout } : {})
|
|
400
|
+
...(h.timeout !== undefined ? { timeout: h.timeout } : {}),
|
|
401
|
+
...(h.async ? { async: true } : {})
|
|
342
402
|
};
|
|
343
403
|
});
|
|
344
404
|
|
package/src/commands/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* All Commands - Category Exports
|
|
3
|
-
*
|
|
4
|
-
* Commands organized by functional category.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export * from './state/index.js';
|
|
8
|
-
export * from './validation/index.js';
|
|
9
|
-
export * from './templates/index.js';
|
|
10
|
-
export * from './project/index.js';
|
|
11
|
-
export * from './tasks/index.js';
|
|
12
|
-
export * from './agents/index.js';
|
|
13
|
-
export * from './utils/index.js';
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Checkpoint Save/Restore Command
|
|
3
|
-
*
|
|
4
|
-
* Save and restore full context at phase boundaries.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* morph-spec checkpoint save <feature-name>
|
|
8
|
-
* morph-spec checkpoint restore <feature-name>
|
|
9
|
-
* morph-spec checkpoint list <feature-name>
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import fs from 'fs';
|
|
13
|
-
import path from 'path';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
import { loadState, saveState, getFeature } from '../../core/state/state-manager.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get checkpoints directory for a feature
|
|
19
|
-
*/
|
|
20
|
-
function getCheckpointsDir(featureName) {
|
|
21
|
-
return path.join(process.cwd(), '.morph/checkpoints', featureName);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Save checkpoint command
|
|
26
|
-
*/
|
|
27
|
-
export async function checkpointSaveCommand(featureName, options = {}) {
|
|
28
|
-
if (!featureName) {
|
|
29
|
-
console.error(chalk.red('Error: Feature name required'));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const feature = getFeature(featureName);
|
|
34
|
-
if (!feature) {
|
|
35
|
-
console.error(chalk.red(`Error: Feature '${featureName}' not found`));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const outputDir = path.join(process.cwd(), '.morph/features', featureName);
|
|
40
|
-
if (!fs.existsSync(outputDir)) {
|
|
41
|
-
console.error(chalk.red(`Error: No outputs found for '${featureName}'`));
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Create checkpoint directory with timestamp
|
|
46
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
47
|
-
const label = options.label || feature.phase;
|
|
48
|
-
const checkpointName = `${label}_${timestamp}`;
|
|
49
|
-
const checkpointDir = path.join(getCheckpointsDir(featureName), checkpointName);
|
|
50
|
-
fs.mkdirSync(checkpointDir, { recursive: true });
|
|
51
|
-
|
|
52
|
-
// Copy all output files
|
|
53
|
-
const files = fs.readdirSync(outputDir);
|
|
54
|
-
let fileCount = 0;
|
|
55
|
-
for (const file of files) {
|
|
56
|
-
const src = path.join(outputDir, file);
|
|
57
|
-
const stat = fs.statSync(src);
|
|
58
|
-
if (stat.isFile()) {
|
|
59
|
-
fs.copyFileSync(src, path.join(checkpointDir, file));
|
|
60
|
-
fileCount++;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Save state snapshot
|
|
65
|
-
const stateSnapshot = {
|
|
66
|
-
feature: { ...feature },
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
phase: feature.phase,
|
|
69
|
-
label,
|
|
70
|
-
files: files.filter(f => fs.statSync(path.join(outputDir, f)).isFile())
|
|
71
|
-
};
|
|
72
|
-
fs.writeFileSync(
|
|
73
|
-
path.join(checkpointDir, '_checkpoint.json'),
|
|
74
|
-
JSON.stringify(stateSnapshot, null, 2)
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
console.log(chalk.green(`\n✓ Checkpoint saved: ${checkpointName}`));
|
|
78
|
-
console.log(chalk.gray(` Phase: ${feature.phase}`));
|
|
79
|
-
console.log(chalk.gray(` Files: ${fileCount}`));
|
|
80
|
-
console.log(chalk.gray(` Path: ${checkpointDir}\n`));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Restore checkpoint command
|
|
85
|
-
*/
|
|
86
|
-
export async function checkpointRestoreCommand(featureName, checkpointName, options = {}) {
|
|
87
|
-
if (!featureName) {
|
|
88
|
-
console.error(chalk.red('Error: Feature name required'));
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const checkpointsDir = getCheckpointsDir(featureName);
|
|
93
|
-
if (!fs.existsSync(checkpointsDir)) {
|
|
94
|
-
console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Find checkpoint to restore
|
|
99
|
-
let targetDir;
|
|
100
|
-
if (checkpointName) {
|
|
101
|
-
targetDir = path.join(checkpointsDir, checkpointName);
|
|
102
|
-
if (!fs.existsSync(targetDir)) {
|
|
103
|
-
console.error(chalk.red(`Error: Checkpoint '${checkpointName}' not found`));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
// Restore latest
|
|
108
|
-
const checkpoints = fs.readdirSync(checkpointsDir)
|
|
109
|
-
.filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
|
|
110
|
-
.sort()
|
|
111
|
-
.reverse();
|
|
112
|
-
|
|
113
|
-
if (checkpoints.length === 0) {
|
|
114
|
-
console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
targetDir = path.join(checkpointsDir, checkpoints[0]);
|
|
119
|
-
checkpointName = checkpoints[0];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Read checkpoint metadata
|
|
123
|
-
const metaPath = path.join(targetDir, '_checkpoint.json');
|
|
124
|
-
if (!fs.existsSync(metaPath)) {
|
|
125
|
-
console.error(chalk.red('Error: Checkpoint metadata not found'));
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
130
|
-
|
|
131
|
-
// Restore output files
|
|
132
|
-
const outputDir = path.join(process.cwd(), '.morph/features', featureName);
|
|
133
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
134
|
-
|
|
135
|
-
const files = fs.readdirSync(targetDir).filter(f => f !== '_checkpoint.json');
|
|
136
|
-
for (const file of files) {
|
|
137
|
-
fs.copyFileSync(path.join(targetDir, file), path.join(outputDir, file));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Restore state
|
|
141
|
-
const state = loadState();
|
|
142
|
-
if (state.features[featureName] && meta.feature) {
|
|
143
|
-
// Preserve some current fields, restore phase and outputs
|
|
144
|
-
state.features[featureName].phase = meta.feature.phase;
|
|
145
|
-
state.features[featureName].outputs = meta.feature.outputs;
|
|
146
|
-
state.features[featureName].tasks = meta.feature.tasks;
|
|
147
|
-
state.features[featureName].approvalGates = meta.feature.approvalGates;
|
|
148
|
-
state.features[featureName].updatedAt = new Date().toISOString();
|
|
149
|
-
saveState(state);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(chalk.green(`\n✓ Checkpoint restored: ${checkpointName}`));
|
|
153
|
-
console.log(chalk.gray(` Phase: ${meta.phase}`));
|
|
154
|
-
console.log(chalk.gray(` Files restored: ${files.length}`));
|
|
155
|
-
console.log(chalk.gray(` Original date: ${meta.timestamp}\n`));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* List checkpoints command
|
|
160
|
-
*/
|
|
161
|
-
export async function checkpointListCommand(featureName, options = {}) {
|
|
162
|
-
if (!featureName) {
|
|
163
|
-
console.error(chalk.red('Error: Feature name required'));
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const checkpointsDir = getCheckpointsDir(featureName);
|
|
168
|
-
if (!fs.existsSync(checkpointsDir)) {
|
|
169
|
-
console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const checkpoints = fs.readdirSync(checkpointsDir)
|
|
174
|
-
.filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
|
|
175
|
-
.sort()
|
|
176
|
-
.reverse();
|
|
177
|
-
|
|
178
|
-
if (checkpoints.length === 0) {
|
|
179
|
-
console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// JSON output
|
|
184
|
-
if (options.json) {
|
|
185
|
-
const list = checkpoints.map(cp => {
|
|
186
|
-
const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
|
|
187
|
-
const meta = fs.existsSync(metaPath) ? JSON.parse(fs.readFileSync(metaPath, 'utf8')) : {};
|
|
188
|
-
return { name: cp, phase: meta.phase, timestamp: meta.timestamp, files: meta.files?.length || 0 };
|
|
189
|
-
});
|
|
190
|
-
console.log(JSON.stringify({ feature: featureName, checkpoints: list }, null, 2));
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Visual output
|
|
195
|
-
console.log(chalk.cyan(`\nCheckpoints for '${featureName}':\n`));
|
|
196
|
-
|
|
197
|
-
for (const cp of checkpoints) {
|
|
198
|
-
const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
|
|
199
|
-
let meta = {};
|
|
200
|
-
if (fs.existsSync(metaPath)) {
|
|
201
|
-
meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const date = meta.timestamp ? new Date(meta.timestamp).toLocaleString() : 'unknown';
|
|
205
|
-
console.log(chalk.white(` ${cp}`));
|
|
206
|
-
console.log(chalk.gray(` Phase: ${meta.phase || 'unknown'} | Files: ${meta.files?.length || '?'} | ${date}`));
|
|
207
|
-
}
|
|
208
|
-
console.log('');
|
|
209
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase State Machine - Enforces valid phase transitions
|
|
3
|
-
*
|
|
4
|
-
* Prevents invalid workflow jumps and ensures sequential phase progression.
|
|
5
|
-
* Based on the MORPH 5-phase workflow.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Valid phase transitions map
|
|
10
|
-
* Each phase can only transition to specific next phases
|
|
11
|
-
*/
|
|
12
|
-
const VALID_TRANSITIONS = {
|
|
13
|
-
'proposal': ['uiux', 'plan'],
|
|
14
|
-
'uiux': ['plan'],
|
|
15
|
-
'plan': ['implement'],
|
|
16
|
-
'implement': ['review'],
|
|
17
|
-
'review': []
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Phase display names for error messages
|
|
22
|
-
*/
|
|
23
|
-
const PHASE_NAMES = {
|
|
24
|
-
'proposal': 'Proposal (Phase 0)',
|
|
25
|
-
'uiux': 'UI/UX Design (Phase 1)',
|
|
26
|
-
'plan': 'Plan (Phase 2)',
|
|
27
|
-
'implement': 'Implement (Phase 3)',
|
|
28
|
-
'review': 'Quality Review (Phase 4)'
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Optional phases that can be skipped
|
|
33
|
-
*/
|
|
34
|
-
const OPTIONAL_PHASES = ['uiux'];
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Validate if a phase transition is allowed
|
|
38
|
-
* @param {string} fromPhase - Current phase
|
|
39
|
-
* @param {string} toPhase - Target phase
|
|
40
|
-
* @returns {boolean} True if transition is valid
|
|
41
|
-
*/
|
|
42
|
-
export function isValidTransition(fromPhase, toPhase) {
|
|
43
|
-
if (!fromPhase || !toPhase) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const validNextPhases = VALID_TRANSITIONS[fromPhase];
|
|
48
|
-
return validNextPhases ? validNextPhases.includes(toPhase) : false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get list of valid next phases for a given phase
|
|
53
|
-
* @param {string} currentPhase - Current phase
|
|
54
|
-
* @returns {string[]} Array of valid next phase names
|
|
55
|
-
*/
|
|
56
|
-
export function getValidNextPhases(currentPhase) {
|
|
57
|
-
return VALID_TRANSITIONS[currentPhase] || [];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get display name for a phase
|
|
62
|
-
* @param {string} phase - Phase identifier
|
|
63
|
-
* @returns {string} Human-readable phase name
|
|
64
|
-
*/
|
|
65
|
-
export function getPhaseDisplayName(phase) {
|
|
66
|
-
return PHASE_NAMES[phase] || phase;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if a phase is optional
|
|
71
|
-
* @param {string} phase - Phase identifier
|
|
72
|
-
* @returns {boolean} True if phase is optional
|
|
73
|
-
*/
|
|
74
|
-
export function isOptionalPhase(phase) {
|
|
75
|
-
return OPTIONAL_PHASES.includes(phase);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Validate transition and throw error if invalid
|
|
80
|
-
* @param {string} fromPhase - Current phase
|
|
81
|
-
* @param {string} toPhase - Target phase
|
|
82
|
-
* @throws {Error} If transition is invalid
|
|
83
|
-
*/
|
|
84
|
-
export function validateTransition(fromPhase, toPhase) {
|
|
85
|
-
if (!fromPhase) {
|
|
86
|
-
throw new Error('Current phase is undefined. Cannot validate transition.');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!toPhase) {
|
|
90
|
-
throw new Error('Target phase is undefined. Cannot validate transition.');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!isValidTransition(fromPhase, toPhase)) {
|
|
94
|
-
const validPhases = getValidNextPhases(fromPhase);
|
|
95
|
-
const fromDisplay = getPhaseDisplayName(fromPhase);
|
|
96
|
-
const toDisplay = getPhaseDisplayName(toPhase);
|
|
97
|
-
|
|
98
|
-
throw new Error(
|
|
99
|
-
`Invalid phase transition: ${fromDisplay} → ${toDisplay}\n\n` +
|
|
100
|
-
`Valid next phases from ${fromDisplay}:\n` +
|
|
101
|
-
validPhases.map(p => ` • ${getPhaseDisplayName(p)}`).join('\n') +
|
|
102
|
-
'\n\n' +
|
|
103
|
-
'You cannot skip phases in the MORPH workflow.\n' +
|
|
104
|
-
'To override this check, use the --force flag (not recommended).'
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get the sequential order of phases
|
|
111
|
-
* @returns {string[]} Ordered list of phases
|
|
112
|
-
*/
|
|
113
|
-
export function getPhaseSequence() {
|
|
114
|
-
return ['proposal', 'uiux', 'plan', 'implement', 'review'];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get phase order index (for comparison)
|
|
119
|
-
* @param {string} phase - Phase identifier
|
|
120
|
-
* @returns {number} Index in sequence (-1 if not found)
|
|
121
|
-
*/
|
|
122
|
-
export function getPhaseOrder(phase) {
|
|
123
|
-
return getPhaseSequence().indexOf(phase);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Check if target phase is ahead of current phase
|
|
128
|
-
* @param {string} currentPhase - Current phase
|
|
129
|
-
* @param {string} targetPhase - Target phase
|
|
130
|
-
* @returns {boolean} True if target is ahead
|
|
131
|
-
*/
|
|
132
|
-
export function isPhaseAhead(currentPhase, targetPhase) {
|
|
133
|
-
return getPhaseOrder(targetPhase) > getPhaseOrder(currentPhase);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Check if target phase is behind current phase (rollback scenario)
|
|
138
|
-
* @param {string} currentPhase - Current phase
|
|
139
|
-
* @param {string} targetPhase - Target phase
|
|
140
|
-
* @returns {boolean} True if target is behind
|
|
141
|
-
*/
|
|
142
|
-
export function isPhaseRollback(currentPhase, targetPhase) {
|
|
143
|
-
return getPhaseOrder(targetPhase) < getPhaseOrder(currentPhase);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get all skipped phases between two phases
|
|
148
|
-
* @param {string} fromPhase - Starting phase
|
|
149
|
-
* @param {string} toPhase - Ending phase
|
|
150
|
-
* @returns {string[]} Array of skipped phases
|
|
151
|
-
*/
|
|
152
|
-
export function getSkippedPhases(fromPhase, toPhase) {
|
|
153
|
-
const sequence = getPhaseSequence();
|
|
154
|
-
const fromIndex = sequence.indexOf(fromPhase);
|
|
155
|
-
const toIndex = sequence.indexOf(toPhase);
|
|
156
|
-
|
|
157
|
-
if (fromIndex === -1 || toIndex === -1 || toIndex <= fromIndex) {
|
|
158
|
-
return [];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return sequence.slice(fromIndex + 1, toIndex);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Validate that no required phases were skipped
|
|
166
|
-
* @param {string} fromPhase - Starting phase
|
|
167
|
-
* @param {string} toPhase - Ending phase
|
|
168
|
-
* @throws {Error} If required phases were skipped
|
|
169
|
-
*/
|
|
170
|
-
export function validateNoRequiredPhasesSkipped(fromPhase, toPhase) {
|
|
171
|
-
const skipped = getSkippedPhases(fromPhase, toPhase);
|
|
172
|
-
const requiredSkipped = skipped.filter(p => !isOptionalPhase(p));
|
|
173
|
-
|
|
174
|
-
if (requiredSkipped.length > 0) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
`Cannot skip required phases:\n` +
|
|
177
|
-
requiredSkipped.map(p => ` • ${getPhaseDisplayName(p)}`).join('\n') +
|
|
178
|
-
'\n\nYou must complete each required phase in sequence.'
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Get comprehensive phase information
|
|
185
|
-
* @param {string} phase - Phase identifier
|
|
186
|
-
* @returns {Object} Phase metadata
|
|
187
|
-
*/
|
|
188
|
-
export function getPhaseInfo(phase) {
|
|
189
|
-
return {
|
|
190
|
-
id: phase,
|
|
191
|
-
displayName: getPhaseDisplayName(phase),
|
|
192
|
-
order: getPhaseOrder(phase),
|
|
193
|
-
isOptional: isOptionalPhase(phase),
|
|
194
|
-
validNextPhases: getValidNextPhases(phase),
|
|
195
|
-
canTransitionTo: (targetPhase) => isValidTransition(phase, targetPhase)
|
|
196
|
-
};
|
|
197
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react';
|
|
2
|
-
|
|
3
|
-
export function useDetailPanel() {
|
|
4
|
-
const [panel, setPanel] = useState({ open: false, title: '', content: null });
|
|
5
|
-
|
|
6
|
-
const openPanel = useCallback((title, content) => {
|
|
7
|
-
setPanel({ open: true, title, content });
|
|
8
|
-
}, []);
|
|
9
|
-
|
|
10
|
-
const closePanel = useCallback(() => {
|
|
11
|
-
setPanel((prev) => ({ ...prev, open: false }));
|
|
12
|
-
}, []);
|
|
13
|
-
|
|
14
|
-
return { panel, openPanel, closePanel };
|
|
15
|
-
}
|