@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where each kind of thing lives in the app. The ONE map of entity → URL, so a
|
|
6
|
+
* persona, standard or skill reads as a link everywhere it is mentioned.
|
|
7
|
+
* A feature belongs to a project: its links come from `featureHref` (lib/project.js).
|
|
8
|
+
*/
|
|
9
|
+
export function entityHref(kind, id) {
|
|
10
|
+
const q = (page) => `/${page}?sel=${encodeURIComponent(id)}`;
|
|
11
|
+
switch (kind) {
|
|
12
|
+
case 'persona': return q('agents');
|
|
13
|
+
case 'skill': return q('skills');
|
|
14
|
+
case 'standard': return q('standards');
|
|
15
|
+
case 'rule': return q('rules');
|
|
16
|
+
case 'hook': return q('hooks');
|
|
17
|
+
case 'phase': return q('phases');
|
|
18
|
+
case 'guide': return `/guide/${encodeURIComponent(id)}`;
|
|
19
|
+
case 'rubric': return q('rubrics');
|
|
20
|
+
default: return '#';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `standards/backend/dotnet/testing.md` (as tasks.json cites it) → the registry `path`. */
|
|
25
|
+
export function standardKey(ref) {
|
|
26
|
+
return String(ref).replace(/^\.?\/?(\.morph\/framework\/)?standards\//, '');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TONE = 'text-[var(--color-phase-proposal)] hover:underline underline-offset-2';
|
|
30
|
+
|
|
31
|
+
export default function EntityLink({ kind, id, children, className = '', chip = false, plain = false, title }) {
|
|
32
|
+
const cls = chip
|
|
33
|
+
? `inline-flex items-center rounded-full px-2 py-0.5 font-mono text-[11px] text-[var(--color-dim)] shadow-[inset_0_0_0_1px_var(--color-hair)] transition-colors hover:text-[var(--color-fg)] hover:shadow-[inset_0_0_0_1px_var(--color-hair-strong)] ${className}`
|
|
34
|
+
: plain ? className : `${TONE} ${className}`;
|
|
35
|
+
return <Link to={entityHref(kind, id)} className={cls} title={title}>{children ?? id}</Link>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The selected item of a knowledge page, kept in `?sel=` so a panel can be
|
|
40
|
+
* linked, bookmarked and reached from anywhere with EntityLink.
|
|
41
|
+
*
|
|
42
|
+
* @returns {[string|null, (id: string|null) => void]}
|
|
43
|
+
*/
|
|
44
|
+
export function useSelection() {
|
|
45
|
+
const [params, setParams] = useSearchParams();
|
|
46
|
+
const sel = params.get('sel');
|
|
47
|
+
const setSel = useCallback((id) => {
|
|
48
|
+
setParams((p) => {
|
|
49
|
+
const n = new URLSearchParams(p);
|
|
50
|
+
if (id) n.set('sel', id); else n.delete('sel');
|
|
51
|
+
return n;
|
|
52
|
+
}, { replace: false });
|
|
53
|
+
}, [setParams]);
|
|
54
|
+
return [sel, setSel];
|
|
55
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Catches a page that throws while rendering and shows the error in place of
|
|
5
|
+
* the page, so the rest of the layout (the sidebar) keeps working.
|
|
6
|
+
*
|
|
7
|
+
* Without it one broken page unmounts the whole tree: 8.38.0 opened on
|
|
8
|
+
* /agents, which threw a ReferenceError, and the dashboard was a black screen.
|
|
9
|
+
*
|
|
10
|
+
* Mount it with `key={location.pathname}`: a boundary that already caught an
|
|
11
|
+
* error stays in fallback until it remounts, and the key remounts it when the
|
|
12
|
+
* sidebar navigates to another page.
|
|
13
|
+
*/
|
|
14
|
+
export default class ErrorBoundary extends Component {
|
|
15
|
+
constructor(props) {
|
|
16
|
+
super(props);
|
|
17
|
+
this.state = { error: null };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getDerivedStateFromError(error) {
|
|
21
|
+
return { error };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
componentDidCatch(error, info) {
|
|
25
|
+
console.error(`[dashboard] ${this.props.page || 'page'} failed to render:`, error, info?.componentStack);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
render() {
|
|
29
|
+
const { error } = this.state;
|
|
30
|
+
if (!error) return this.props.children;
|
|
31
|
+
return (
|
|
32
|
+
<div role="alert" className="p-6 max-w-3xl">
|
|
33
|
+
<h2 className="text-red-400 text-sm font-semibold uppercase tracking-wide mb-2">
|
|
34
|
+
This page failed to render
|
|
35
|
+
</h2>
|
|
36
|
+
<p className="text-gray-300 text-sm mb-3">
|
|
37
|
+
<span className="font-mono text-gray-400">{this.props.page}</span> threw while rendering.
|
|
38
|
+
The other pages still work — pick one in the sidebar.
|
|
39
|
+
</p>
|
|
40
|
+
<pre className="p-3 bg-gray-900 border border-red-900 rounded text-xs text-red-300 whitespace-pre-wrap font-mono overflow-x-auto">
|
|
41
|
+
{error.name}: {error.message}
|
|
42
|
+
</pre>
|
|
43
|
+
{error.stack && (
|
|
44
|
+
<details className="mt-2">
|
|
45
|
+
<summary className="text-gray-500 text-xs cursor-pointer">Stack trace</summary>
|
|
46
|
+
<pre className="mt-1 p-3 bg-gray-950 border border-gray-800 rounded text-[10px] text-gray-500 whitespace-pre-wrap font-mono overflow-x-auto">
|
|
47
|
+
{error.stack}
|
|
48
|
+
</pre>
|
|
49
|
+
</details>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { ReactFlow, Controls, MiniMap, Background } from '@xyflow/react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { ReactFlow, Controls, MiniMap, Background } from '@xyflow/react';
|
|
2
|
+
|
|
3
|
+
// The React Flow attribution stays: hiding it is licensed to Pro subscribers only.
|
|
4
|
+
export default function FlowGraph({ nodes, edges, nodeTypes, onNodeClick, fitView = true, className = '' }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className={`w-full h-full ${className}`} style={{ minHeight: '500px' }}>
|
|
7
|
+
<ReactFlow
|
|
8
|
+
nodes={nodes} edges={edges} nodeTypes={nodeTypes} onNodeClick={onNodeClick}
|
|
9
|
+
fitView={fitView} fitViewOptions={{ padding: 0.15, maxZoom: 1.1 }} minZoom={0.2}
|
|
10
|
+
|
|
11
|
+
defaultEdgeOptions={{ type: 'smoothstep', animated: false, style: { stroke: '#3a4250', strokeWidth: 1.5 } }}
|
|
12
|
+
>
|
|
13
|
+
<Background color="rgb(255 255 255 / 0.05)" gap={22} />
|
|
14
|
+
<Controls position="bottom-right" />
|
|
15
|
+
<MiniMap nodeColor={(node) => node.data?.color || '#4a5260'} maskColor="rgba(5, 5, 5, 0.72)" position="bottom-left" pannable zoomable />
|
|
16
|
+
</ReactFlow>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import morphData from 'virtual:morph-data';
|
|
2
|
+
import Markdown from './Markdown.jsx';
|
|
3
|
+
import EntityLink, { standardKey } from './EntityLink.jsx';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Names of things that have a page. Built once from the knowledge module.
|
|
7
|
+
*/
|
|
8
|
+
const skillIds = new Set((morphData.skills || []).map((s) => s.id));
|
|
9
|
+
const personaIds = new Set((morphData.agents?.list || []).map((a) => a.id));
|
|
10
|
+
const standardByKey = new Map();
|
|
11
|
+
for (const s of morphData.standards?.list || []) {
|
|
12
|
+
standardByKey.set(s.id, s.id);
|
|
13
|
+
standardByKey.set(s.path, s.id);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What an inline code span names, if it names something with a page:
|
|
18
|
+
* `morph-plan` or `/morph-plan` → skill, `morph-dotnet-senior` → persona,
|
|
19
|
+
* a standard id or path → standard.
|
|
20
|
+
*
|
|
21
|
+
* @returns {{ kind: string, id: string } | null}
|
|
22
|
+
*/
|
|
23
|
+
export function resolveMention(text) {
|
|
24
|
+
const t = String(text).trim();
|
|
25
|
+
const bare = t.replace(/^\//, '');
|
|
26
|
+
if (skillIds.has(bare)) return { kind: 'skill', id: bare };
|
|
27
|
+
const persona = /^morph-(.+)$/.exec(bare);
|
|
28
|
+
if (persona && personaIds.has(persona[1])) return { kind: 'persona', id: persona[1] };
|
|
29
|
+
const std = standardByKey.get(standardKey(t));
|
|
30
|
+
if (std) return { kind: 'standard', id: std };
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function InlineCode({ className, children, node, ...rest }) {
|
|
35
|
+
const text = Array.isArray(children) ? children.join('') : String(children ?? '');
|
|
36
|
+
const block = Boolean(className) || text.includes('\n');
|
|
37
|
+
if (!block) {
|
|
38
|
+
const hit = resolveMention(text);
|
|
39
|
+
if (hit) {
|
|
40
|
+
return (
|
|
41
|
+
<EntityLink kind={hit.kind} id={hit.id} className="no-underline">
|
|
42
|
+
<code>{text}</code>
|
|
43
|
+
</EntityLink>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return <code className={className} {...rest}>{children}</code>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Markdown for framework and feature text: code spans that name a skill, persona or standard become links. */
|
|
51
|
+
export default function KnowledgeMarkdown({ components, ...props }) {
|
|
52
|
+
return <Markdown {...props} components={{ code: InlineCode, ...components }} />;
|
|
53
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import ReactMarkdown from 'react-markdown';
|
|
2
|
+
import remarkGfm from 'remark-gfm';
|
|
3
|
+
import rehypeSlug from 'rehype-slug';
|
|
4
|
+
import { splitFrontmatter } from '../lib/format.js';
|
|
5
|
+
|
|
6
|
+
const REMARK = [remarkGfm];
|
|
7
|
+
const REHYPE = [rehypeSlug];
|
|
8
|
+
|
|
9
|
+
/** Frontmatter keys worth showing, in the order a reader wants them. */
|
|
10
|
+
const META_ORDER = ['name', 'description', 'when-to-invoke', 'user-invokable', 'stacks', 'version', 'cliVersion'];
|
|
11
|
+
|
|
12
|
+
function MetaBlock({ meta }) {
|
|
13
|
+
const keys = Object.keys(meta).sort((a, b) => {
|
|
14
|
+
const ia = META_ORDER.indexOf(a); const ib = META_ORDER.indexOf(b);
|
|
15
|
+
return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
|
|
16
|
+
});
|
|
17
|
+
return (
|
|
18
|
+
<dl className="mb-6 grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1.5 rounded-xl bg-white/[0.03] px-4 py-3 text-sm shadow-[inset_0_0_0_1px_var(--color-hair)]">
|
|
19
|
+
{keys.map((k) => (
|
|
20
|
+
<div key={k} className="contents">
|
|
21
|
+
<dt className="font-mono text-xs leading-6 text-[var(--color-faint)]">{k}</dt>
|
|
22
|
+
<dd className="leading-6 text-[var(--color-dim)]">{meta[k] || '—'}</dd>
|
|
23
|
+
</div>
|
|
24
|
+
))}
|
|
25
|
+
</dl>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Rendered markdown for every long text in the app: feature artifacts, skills,
|
|
31
|
+
* standards, the charter. Frontmatter is lifted into a metadata block instead
|
|
32
|
+
* of being printed as text.
|
|
33
|
+
*
|
|
34
|
+
* @param {{ text: string, components?: object, className?: string, size?: 'md'|'sm' }} props
|
|
35
|
+
* `sm` is for side panels, where the reading column is narrower.
|
|
36
|
+
*/
|
|
37
|
+
export default function Markdown({ text, components, className = '', size = 'md' }) {
|
|
38
|
+
const { meta, body } = splitFrontmatter(text || '');
|
|
39
|
+
return (
|
|
40
|
+
<article className={`prose-morph ${size === 'sm' ? 'prose-morph-sm' : ''} ${className}`}>
|
|
41
|
+
{meta && Object.keys(meta).length > 0 && <MetaBlock meta={meta} />}
|
|
42
|
+
<ReactMarkdown remarkPlugins={REMARK} rehypePlugins={REHYPE} components={components}>
|
|
43
|
+
{body}
|
|
44
|
+
</ReactMarkdown>
|
|
45
|
+
</article>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { PHASES, PHASE_INDEX, GATE_LABEL, phaseMeta } from '../lib/phases.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The phase rail: one segment per phase, a notch after each phase that ends in
|
|
5
|
+
* an approval gate. The same motif is used in the feature list (sm) and in the
|
|
6
|
+
* feature header (lg), so a feature's progress always reads the same way.
|
|
7
|
+
*
|
|
8
|
+
* Segment state is derived, never stored:
|
|
9
|
+
* done — before the current phase, or the whole feature is completed
|
|
10
|
+
* current — the derived phase of an active feature
|
|
11
|
+
* skipped — an optional phase (uiux) the feature went past without its gate
|
|
12
|
+
* todo — after the current phase
|
|
13
|
+
*/
|
|
14
|
+
export function segmentStates(feature) {
|
|
15
|
+
const idx = PHASE_INDEX[feature.phase] ?? 0;
|
|
16
|
+
const finished = feature.status === 'completed';
|
|
17
|
+
const gate = (id) => feature.gates?.find((g) => g.gate === id);
|
|
18
|
+
return PHASES.map((p, i) => {
|
|
19
|
+
let state = 'todo';
|
|
20
|
+
if (p.optional && !gate(p.id)?.approved && (i < idx || finished)) state = 'skipped';
|
|
21
|
+
else if (finished || i < idx) state = 'done';
|
|
22
|
+
else if (i === idx) state = 'current';
|
|
23
|
+
return { ...p, state, gate: gate(p.id) || null };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function notchState(g, segState) {
|
|
28
|
+
if (!g || g.skipped || segState === 'skipped') return null;
|
|
29
|
+
if (g.approved) return 'approved';
|
|
30
|
+
if (g.paused) return 'paused';
|
|
31
|
+
return 'pending';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const NOTCH_CLASS = {
|
|
35
|
+
approved: 'bg-[var(--color-ok)] shadow-[0_0_0_3px_rgb(158_206_106_/_0.15)]',
|
|
36
|
+
paused: 'bg-transparent ring-2 ring-[var(--color-warn)] animate-pulse',
|
|
37
|
+
pending: 'bg-transparent ring-1 ring-[var(--color-hair-strong)]',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function gateTitle(g) {
|
|
41
|
+
const label = GATE_LABEL[g.gate] || g.gate;
|
|
42
|
+
if (g.approved) return `${label}: aprovado${g.approvedBy ? ` por ${g.approvedBy}` : ''}`;
|
|
43
|
+
if (g.paused) return `${label}: pausado${g.reason ? `, ${g.reason}` : ''}`;
|
|
44
|
+
return `${label}: pendente`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default function PhaseRail({ feature, size = 'sm' }) {
|
|
48
|
+
const segments = segmentStates(feature);
|
|
49
|
+
const lg = size === 'lg';
|
|
50
|
+
const current = phaseMeta(feature.phase);
|
|
51
|
+
const summary = feature.status === 'completed'
|
|
52
|
+
? 'Concluída'
|
|
53
|
+
: `Fase atual: ${current.label}${feature.inEvaluation ? ' (avaliação em andamento)' : ''}`;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={lg ? 'w-full' : 'w-full min-w-[9rem]'} role="img" aria-label={summary}>
|
|
57
|
+
<div className="flex items-center">
|
|
58
|
+
{segments.map((s, i) => {
|
|
59
|
+
const notch = notchState(s.gate, s.state);
|
|
60
|
+
return (
|
|
61
|
+
<div key={s.id} className="flex items-center flex-1 min-w-0" title={s.label}>
|
|
62
|
+
<div
|
|
63
|
+
className={[
|
|
64
|
+
'relative flex-1 overflow-hidden rounded-full',
|
|
65
|
+
lg ? 'h-2.5' : 'h-1.5',
|
|
66
|
+
s.state === 'skipped' ? 'border border-dashed border-[var(--color-hair-strong)] bg-transparent' : '',
|
|
67
|
+
s.state === 'todo' ? 'bg-white/[0.07]' : '',
|
|
68
|
+
].join(' ')}
|
|
69
|
+
style={s.state === 'done' || s.state === 'current' ? { background: s.color, opacity: s.state === 'done' ? 0.78 : 1 } : undefined}
|
|
70
|
+
>
|
|
71
|
+
{s.state === 'current' && feature.status !== 'completed' && (
|
|
72
|
+
<span className="rail-sheen absolute inset-0" aria-hidden="true" />
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
{notch && (
|
|
76
|
+
<span
|
|
77
|
+
title={gateTitle(s.gate)}
|
|
78
|
+
className={['shrink-0 rounded-full', lg ? 'mx-1.5 h-3 w-3' : 'mx-1 h-2 w-2', NOTCH_CLASS[notch]].join(' ')}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
{!notch && i < segments.length - 1 && <span className={lg ? 'w-3' : 'w-2'} />}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
</div>
|
|
86
|
+
{lg && (
|
|
87
|
+
<div className="mt-2.5 flex">
|
|
88
|
+
{segments.map((s) => (
|
|
89
|
+
<div key={s.id} className="flex-1 min-w-0 pr-3">
|
|
90
|
+
<p
|
|
91
|
+
className={[
|
|
92
|
+
'truncate text-xs',
|
|
93
|
+
s.state === 'current' ? 'font-medium text-[var(--color-fg)]' : 'text-[var(--color-dim)]',
|
|
94
|
+
s.state === 'skipped' ? 'line-through decoration-[var(--color-faint)]' : '',
|
|
95
|
+
].join(' ')}
|
|
96
|
+
>
|
|
97
|
+
{s.label}
|
|
98
|
+
</p>
|
|
99
|
+
{s.state === 'skipped' && <p className="truncate text-[11px] text-[var(--color-faint)]">fase não usada</p>}
|
|
100
|
+
{s.state !== 'skipped' && s.gate && !s.gate.skipped && (
|
|
101
|
+
<p className="truncate text-[11px] text-[var(--color-faint)]">
|
|
102
|
+
{s.gate.approved ? `aprovado${s.gate.approvedBy ? `, ${s.gate.approvedBy}` : ''}` : s.gate.paused ? 'gate pausado' : 'gate pendente'}
|
|
103
|
+
</p>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -1,22 +1,40 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { MagnifyingGlass } from '@phosphor-icons/react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The toolbar of the knowledge pages: a search box and a few selects. The
|
|
6
|
+
* `value` of each filter is controlled by the page; the search text is local.
|
|
7
|
+
*/
|
|
8
|
+
export default function SearchFilter({ placeholder = 'Buscar', filters = [], onSearchChange, onFilterChange, children }) {
|
|
9
|
+
const [search, setSearch] = useState('');
|
|
10
|
+
function handleSearchChange(e) {
|
|
11
|
+
setSearch(e.target.value);
|
|
12
|
+
onSearchChange?.(e.target.value);
|
|
13
|
+
}
|
|
14
|
+
return (
|
|
15
|
+
<div className="relative z-10 flex flex-wrap items-center gap-2 border-b border-[var(--color-hair)] bg-[var(--color-ink)]/70 px-4 py-3 backdrop-blur md:px-6">
|
|
16
|
+
<label className="relative min-w-[12rem] flex-1">
|
|
17
|
+
<span className="sr-only">{placeholder}</span>
|
|
18
|
+
<MagnifyingGlass weight="light" size={16} className="pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 text-[var(--color-faint)]" />
|
|
19
|
+
<input
|
|
20
|
+
type="text" value={search} onChange={handleSearchChange} placeholder={placeholder}
|
|
21
|
+
onKeyDown={(e) => { if (e.key === 'Escape') handleSearchChange({ target: { value: '' } }); }}
|
|
22
|
+
className="fluid w-full rounded-full bg-white/[0.04] py-2 pl-10 pr-4 text-sm text-[var(--color-fg)] shadow-[inset_0_0_0_1px_var(--color-hair)] outline-none transition-shadow placeholder:text-[var(--color-faint)] focus:shadow-[inset_0_0_0_1px_var(--color-hair-strong)]"
|
|
23
|
+
/>
|
|
24
|
+
</label>
|
|
25
|
+
{filters.map(({ name, label, options, value }) => (
|
|
26
|
+
<label key={name} className="text-sm">
|
|
27
|
+
<span className="sr-only">{label}</span>
|
|
28
|
+
<select
|
|
29
|
+
value={value} onChange={(e) => onFilterChange?.(name, e.target.value)}
|
|
30
|
+
className="fluid rounded-full bg-white/[0.04] px-3 py-2 text-sm text-[var(--color-fg)] shadow-[inset_0_0_0_1px_var(--color-hair)] outline-none"
|
|
31
|
+
>
|
|
32
|
+
<option value="">{label}: todos</option>
|
|
33
|
+
{options.map((opt) => (<option key={opt} value={opt}>{opt}</option>))}
|
|
34
|
+
</select>
|
|
35
|
+
</label>
|
|
36
|
+
))}
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -1,44 +1,153 @@
|
|
|
1
|
-
import { NavLink } from 'react-router-dom';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ to: '/
|
|
9
|
-
{ to: '/
|
|
10
|
-
{ to: '/
|
|
1
|
+
import { NavLink, useLocation, useNavigate } from 'react-router-dom';
|
|
2
|
+
import {
|
|
3
|
+
MagnifyingGlass, SquaresFour, Kanban, Pulse, BookOpen, FlowArrow, PuzzlePiece, UsersThree, Books, Ruler, Plug, Scales,
|
|
4
|
+
} from '@phosphor-icons/react';
|
|
5
|
+
import { projectHref } from '../lib/project.js';
|
|
6
|
+
|
|
7
|
+
const KNOWLEDGE = [
|
|
8
|
+
{ to: '/guide', label: 'Guia', icon: BookOpen },
|
|
9
|
+
{ to: '/phases', label: 'Fases', icon: FlowArrow },
|
|
10
|
+
{ to: '/skills', label: 'Skills', icon: PuzzlePiece },
|
|
11
|
+
{ to: '/agents', label: 'Personas', icon: UsersThree },
|
|
12
|
+
{ to: '/standards', label: 'Standards', icon: Books },
|
|
13
|
+
{ to: '/rules', label: 'Rules', icon: Ruler },
|
|
14
|
+
{ to: '/hooks', label: 'Hooks', icon: Plug },
|
|
15
|
+
{ to: '/rubrics', label: 'Rubricas', icon: Scales },
|
|
11
16
|
];
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
function Badge({ pulse }) {
|
|
19
|
+
if (!pulse) return null;
|
|
20
|
+
if (pulse.awaiting) {
|
|
21
|
+
return <span className="ml-auto rounded-full bg-[var(--color-warn)]/15 px-1.5 font-mono text-[11px] text-[var(--color-warn)]" title="features esperando você">{pulse.awaiting}</span>;
|
|
22
|
+
}
|
|
23
|
+
if (pulse.working) {
|
|
24
|
+
return <span className="ml-auto h-1.5 w-1.5 rounded-full bg-[var(--color-phase-implement)]" title="agente trabalhando agora" />;
|
|
25
|
+
}
|
|
26
|
+
return pulse.active ? <span className="ml-auto font-mono text-[11px] text-[var(--color-faint)]">{pulse.active}</span> : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const linkClass = ({ isActive }) => [
|
|
30
|
+
'fluid flex items-center gap-2.5 rounded-lg px-3 py-1.5 text-sm transition-colors',
|
|
31
|
+
isActive ? 'bg-white/[0.08] text-[var(--color-fg)] shadow-[inset_0_1px_0_rgb(255_255_255_/_0.06)]' : 'text-[var(--color-dim)] hover:bg-white/[0.04] hover:text-[var(--color-fg)]',
|
|
32
|
+
].join(' ');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Switching project keeps the page: from a feature's detail it lands on the
|
|
36
|
+
* other project's feature list, from Activity on the other project's Activity.
|
|
37
|
+
*/
|
|
38
|
+
function ProjectSwitcher({ portfolio, current }) {
|
|
39
|
+
const navigate = useNavigate();
|
|
40
|
+
const { pathname } = useLocation();
|
|
41
|
+
const onChange = (id) => {
|
|
42
|
+
const sub = /^\/p\/[^/]+\/activity/.test(pathname) ? 'activity' : 'features';
|
|
43
|
+
navigate(projectHref(id, sub));
|
|
44
|
+
};
|
|
45
|
+
return (
|
|
46
|
+
<label className="mx-1 mb-1 block">
|
|
47
|
+
<span className="sr-only">Trocar de projeto</span>
|
|
48
|
+
<select
|
|
49
|
+
value={current || ''}
|
|
50
|
+
onChange={(e) => onChange(e.target.value)}
|
|
51
|
+
className="fluid w-full truncate rounded-lg bg-white/[0.04] px-2.5 py-1.5 text-sm font-medium text-[var(--color-fg)] shadow-[inset_0_0_0_1px_var(--color-hair)] outline-none"
|
|
52
|
+
>
|
|
53
|
+
{portfolio.projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
|
|
54
|
+
</select>
|
|
55
|
+
</label>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default function Sidebar({ portfolio, current, pulse, onSearch }) {
|
|
60
|
+
const projects = portfolio?.projects || [];
|
|
61
|
+
const currentName = projects.find((p) => p.id === current)?.name;
|
|
14
62
|
return (
|
|
15
|
-
<nav className="w-
|
|
16
|
-
<div className="
|
|
17
|
-
<
|
|
18
|
-
morph-spec
|
|
19
|
-
|
|
20
|
-
|
|
63
|
+
<nav aria-label="Navegação principal" className="relative z-10 hidden w-56 shrink-0 flex-col p-3 md:flex">
|
|
64
|
+
<div className="bezel flex flex-1 flex-col">
|
|
65
|
+
<div className="bezel-core flex flex-1 flex-col overflow-y-auto px-2 py-4">
|
|
66
|
+
<p className="px-3 pb-4 text-[15px] font-semibold tracking-tight">morph<span className="text-[var(--color-faint)]">-spec</span></p>
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={onSearch}
|
|
70
|
+
className="fluid mx-1 mb-5 flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm text-[var(--color-faint)] shadow-[inset_0_0_0_1px_var(--color-hair)] transition-colors hover:text-[var(--color-dim)]"
|
|
71
|
+
>
|
|
72
|
+
<MagnifyingGlass weight="light" size={15} /> Buscar
|
|
73
|
+
<kbd className="ml-auto font-mono text-[11px]">Ctrl K</kbd>
|
|
74
|
+
</button>
|
|
75
|
+
|
|
76
|
+
<ul className="mb-4">
|
|
77
|
+
<li>
|
|
78
|
+
<NavLink to="/" end className={linkClass}>
|
|
79
|
+
<SquaresFour weight="light" size={17} aria-hidden="true" /> Portfólio
|
|
80
|
+
{pulse?.awaiting > 0 && <Badge pulse={{ awaiting: pulse.awaiting }} />}
|
|
81
|
+
</NavLink>
|
|
82
|
+
</li>
|
|
83
|
+
</ul>
|
|
84
|
+
|
|
85
|
+
{current && (
|
|
86
|
+
<div className="mb-5">
|
|
87
|
+
<p className="px-3 pb-1.5 text-xs text-[var(--color-faint)]">Projeto</p>
|
|
88
|
+
{projects.length > 1
|
|
89
|
+
? <ProjectSwitcher portfolio={portfolio} current={current} />
|
|
90
|
+
: <p className="truncate px-3 pb-1 text-sm font-medium">{currentName}</p>}
|
|
91
|
+
<ul>
|
|
92
|
+
<li>
|
|
93
|
+
<NavLink to={projectHref(current)} className={linkClass}>
|
|
94
|
+
<Kanban weight="light" size={17} aria-hidden="true" /> Features
|
|
95
|
+
<Badge pulse={pulse?.byProject?.[current]} />
|
|
96
|
+
</NavLink>
|
|
97
|
+
</li>
|
|
98
|
+
<li>
|
|
99
|
+
<NavLink to={projectHref(current, 'activity')} className={linkClass}>
|
|
100
|
+
<Pulse weight="light" size={17} aria-hidden="true" /> Atividade
|
|
101
|
+
</NavLink>
|
|
102
|
+
</li>
|
|
103
|
+
</ul>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
<div className="mb-5">
|
|
108
|
+
<p className="px-3 pb-1.5 text-xs text-[var(--color-faint)]">Como funciona</p>
|
|
109
|
+
<ul>
|
|
110
|
+
{KNOWLEDGE.map(({ to, label, icon: Icon }) => (
|
|
111
|
+
<li key={to}>
|
|
112
|
+
<NavLink to={to} className={linkClass}>
|
|
113
|
+
<Icon weight="light" size={17} aria-hidden="true" />
|
|
114
|
+
{label}
|
|
115
|
+
</NavLink>
|
|
116
|
+
</li>
|
|
117
|
+
))}
|
|
118
|
+
</ul>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
21
121
|
</div>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
122
|
+
</nav>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The same destinations as a compact bar for narrow screens. */
|
|
127
|
+
export function MobileNav({ current, onSearch }) {
|
|
128
|
+
const items = [
|
|
129
|
+
{ to: '/', label: 'Portfólio' },
|
|
130
|
+
...(current ? [{ to: projectHref(current), label: 'Features' }, { to: projectHref(current, 'activity'), label: 'Atividade' }] : []),
|
|
131
|
+
...KNOWLEDGE,
|
|
132
|
+
];
|
|
133
|
+
return (
|
|
134
|
+
<nav aria-label="Navegação" className="sticky top-0 z-20 flex gap-1 overflow-x-auto [scrollbar-width:none] border-b border-[var(--color-hair)] bg-[var(--color-ink)]/85 px-3 py-2 backdrop-blur md:hidden">
|
|
135
|
+
<button type="button" onClick={onSearch} aria-label="Buscar" className="shrink-0 rounded-full px-2.5 py-1 text-[var(--color-dim)]">
|
|
136
|
+
<MagnifyingGlass weight="light" size={16} />
|
|
137
|
+
</button>
|
|
138
|
+
{items.map(({ to, label }) => (
|
|
139
|
+
<NavLink
|
|
140
|
+
key={to}
|
|
141
|
+
to={to}
|
|
142
|
+
end={to === '/'}
|
|
143
|
+
className={({ isActive }) => [
|
|
144
|
+
'shrink-0 rounded-full px-3 py-1 text-sm',
|
|
145
|
+
isActive ? 'bg-white/[0.1] text-[var(--color-fg)]' : 'text-[var(--color-dim)]',
|
|
146
|
+
].join(' ')}
|
|
147
|
+
>
|
|
148
|
+
{label}
|
|
149
|
+
</NavLink>
|
|
150
|
+
))}
|
|
42
151
|
</nav>
|
|
43
152
|
);
|
|
44
153
|
}
|