@polymorphism-tech/morph-spec 3.0.1 → 3.1.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 +52 -88
- package/LICENSE +72 -72
- package/README.md +198 -76
- package/bin/detect-agents.js +3 -1
- package/bin/morph-spec.js +10 -0
- package/bin/render-template.js +5 -4
- package/bin/semantic-detect-agents.js +2 -1
- package/bin/{task-manager.js → task-manager.cjs} +12 -1
- package/bin/validate-agents-skills.js +10 -4
- package/bin/validate-agents.js +4 -3
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +3 -3
- package/docs/templates.md +418 -418
- package/package.json +3 -3
- package/scripts/postinstall.js +132 -132
- package/scripts/reorganize-skills.cjs +1 -1
- package/scripts/validate-agents-structure.cjs +1 -1
- package/scripts/validate-skills.cjs +2 -2
- package/src/commands/analyze-blazor-concurrency.js +193 -193
- package/src/commands/create-story.js +5 -2
- package/src/commands/deploy.js +780 -780
- package/src/commands/detect-agents.js +4 -2
- package/src/commands/generate.js +149 -149
- package/src/commands/lint-fluent.js +352 -352
- package/src/commands/rollback-phase.js +185 -185
- package/src/commands/session-summary.js +291 -291
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/task.js +1 -1
- package/src/commands/troubleshoot.js +222 -222
- package/src/commands/validate-blazor-state.js +210 -210
- package/src/commands/validate-blazor.js +156 -156
- package/src/commands/validate-css.js +84 -84
- package/src/commands/validate-phase.js +221 -221
- package/src/lib/blazor-concurrency-analyzer.js +288 -288
- package/src/lib/blazor-state-validator.js +291 -291
- package/src/lib/blazor-validator.js +374 -374
- package/src/lib/context-generator.js +7 -4
- package/src/lib/css-validator.js +352 -352
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/hook-executor.js +2 -1
- package/src/lib/learning-system.js +520 -520
- package/src/lib/mockup-generator.js +366 -366
- package/src/lib/stack-resolver.js +148 -0
- package/src/lib/standards-context-injector.js +4 -3
- package/src/lib/team-orchestrator.js +2 -1
- package/src/lib/troubleshoot-grep.js +204 -194
- package/src/lib/troubleshoot-index.js +144 -144
- package/src/lib/ui-detector.js +350 -350
- package/src/lib/validation-runner.js +2 -1
- package/src/lib/validators/architecture-validator.js +387 -387
- package/src/lib/validators/package-validator.js +360 -360
- package/src/lib/validators/ui-contrast-validator.js +422 -422
- package/src/utils/file-copier.js +3 -1
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/{content → stacks/blazor-azure}/.azure/README.md +2 -2
- package/{content → stacks/blazor-azure}/.azure/pipelines/pipeline-variables.yml +1 -1
- package/{content → stacks/blazor-azure}/.azure/pipelines/prod-pipeline.yml +1 -1
- package/{content → stacks/blazor-azure}/.azure/pipelines/staging-pipeline.yml +1 -1
- package/{content → stacks/blazor-azure}/.claude/commands/morph-preflight.md +227 -227
- package/{content → stacks/blazor-azure}/.claude/commands/morph-troubleshoot.md +122 -122
- package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-setup.md +1 -1
- package/{content → stacks/blazor-azure}/.morph/docs/workflows/enforcement-pipeline.md +3 -3
- package/{content → stacks/blazor-azure}/.morph/hooks/README.md +12 -12
- package/{content → stacks/blazor-azure}/.morph/standards/agent-teams-workflow.md +2 -2
- package/{content → stacks/blazor-azure}/.morph/standards/migration-guide.md +2 -2
- package/{content → stacks/blazor-azure}/.morph/templates/infra/deploy-checklist.md +426 -426
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +244 -0
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +335 -0
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +189 -0
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +170 -0
- package/stacks/nextjs-supabase/.morph/config/agents.json +345 -0
- package/stacks/nextjs-supabase/.morph/config/config.template.json +92 -0
- package/stacks/nextjs-supabase/.morph/docs/easypanel-setup.md +169 -0
- package/stacks/nextjs-supabase/.morph/docs/supabase-mcp-setup.md +247 -0
- package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/README.md +697 -0
- package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/spec.md +85 -0
- package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/tasks.md +86 -0
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/README.md +498 -0
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/decisions.md +121 -0
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/spec.md +138 -0
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/tasks.md +162 -0
- package/stacks/nextjs-supabase/.morph/project.md +168 -0
- package/stacks/nextjs-supabase/.morph/standards/easypanel-deploy.md +191 -0
- package/stacks/nextjs-supabase/.morph/standards/nextjs-patterns.md +193 -0
- package/stacks/nextjs-supabase/.morph/standards/supabase-auth.md +171 -0
- package/stacks/nextjs-supabase/.morph/standards/supabase-pgvector.md +164 -0
- package/stacks/nextjs-supabase/.morph/standards/supabase-rls.md +179 -0
- package/stacks/nextjs-supabase/.morph/standards/supabase-storage.md +148 -0
- package/stacks/nextjs-supabase/.morph/templates/contracts.cs +173 -0
- package/stacks/nextjs-supabase/.morph/templates/contracts.ts +168 -0
- package/stacks/nextjs-supabase/.morph/templates/decisions.md +115 -0
- package/stacks/nextjs-supabase/.morph/templates/dockerfile-api.dockerfile +38 -0
- package/stacks/nextjs-supabase/.morph/templates/dockerfile-web.dockerfile +48 -0
- package/stacks/nextjs-supabase/.morph/templates/proposal.md +145 -0
- package/stacks/nextjs-supabase/.morph/templates/recap.md +134 -0
- package/stacks/nextjs-supabase/.morph/templates/rls-policy.sql +57 -0
- package/stacks/nextjs-supabase/.morph/templates/spec.md +231 -0
- package/stacks/nextjs-supabase/.morph/templates/supabase-migration.sql +100 -0
- package/stacks/nextjs-supabase/.morph/templates/tasks.md +257 -0
- package/stacks/nextjs-supabase/CLAUDE.md +149 -0
- package/stacks/nextjs-supabase/README.md +112 -0
- /package/{content → stacks/blazor-azure}/.azure/docs/azure-devops-setup.md +0 -0
- /package/{content → stacks/blazor-azure}/.azure/docs/branch-strategy.md +0 -0
- /package/{content → stacks/blazor-azure}/.azure/docs/local-development.md +0 -0
- /package/{content → stacks/blazor-azure}/.azure/pipelines/templates/build-dotnet.yml +0 -0
- /package/{content → stacks/blazor-azure}/.azure/pipelines/templates/deploy-app-service.yml +0 -0
- /package/{content → stacks/blazor-azure}/.azure/pipelines/templates/deploy-container-app.yml +0 -0
- /package/{content → stacks/blazor-azure}/.azure/pipelines/templates/infra-deploy.yml +0 -0
- /package/{content → stacks/blazor-azure}/.claude/commands/morph-apply.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/commands/morph-archive.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/commands/morph-deploy.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/commands/morph-infra.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/commands/morph-proposal.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/commands/morph-status.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/settings.local.json +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/code-review.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/morph-checklist.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/simulation-checklist.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/morph-replicate.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-clarify.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-design.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-tasks.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-uiux.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/resend-email.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-3-technologies/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.claude/skills/level-4-patterns/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/.morphversion +0 -0
- /package/{content → stacks/blazor-azure}/.morph/archive/.gitkeep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/config/agents.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/config/config.template.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/docs/workflows/design-impl.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/docs/workflows/fast-track.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/docs/workflows/full-morph.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/docs/workflows/standard.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/docs/workflows/ui-refresh.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/contracts.ts +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/spec.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/tasks.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/contracts.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/decisions.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/spec.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/tasks.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/contracts.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/spec.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/tasks.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/scheduled-reports/decisions.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/scheduled-reports/proposal.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/scheduled-reports/spec.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/examples/state-v3.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/features/.gitkeep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-agents.sh +0 -0
- /package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-all.sh +0 -0
- /package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-specs.sh +0 -0
- /package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-tests.sh +0 -0
- /package/{content → stacks/blazor-azure}/.morph/hooks/task-completed.js +0 -0
- /package/{content → stacks/blazor-azure}/.morph/hooks/teammate-idle.js +0 -0
- /package/{content → stacks/blazor-azure}/.morph/project.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/schemas/agent.schema.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/schemas/tasks.schema.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/specs/.gitkeep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-blazor-ui.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-production.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-setup.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-workflows.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/architecture.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/azure.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/coding.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/dotnet10-migration.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/fluent-ui-setup.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/passkeys-auth.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/standards/vector-search-rag.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/state.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/CONTEXT-FEATURE.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/CONTEXT.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/FluentDesignTheme.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/MudTheme.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/agent.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/clarify-questions.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/component.razor +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/contracts/Commands.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/contracts/Entities.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/contracts/Queries.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/contracts/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/contracts.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/decisions.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/design-system.css +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/.dockerignore.example +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/Dockerfile.example +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/README.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/app-insights.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/app-service.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/azure-pipelines-deploy.yml +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/container-app-env.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/container-app.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/deploy.ps1 +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/deploy.sh +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/key-vault.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/main.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/parameters.dev.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/parameters.prod.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/parameters.staging.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/sql-database.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/infra/storage.bicep +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/integrations/asaas-client.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/integrations/asaas-webhook.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/integrations/azure-identity-config.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/integrations/clerk-config.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/job.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/migration.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/proposal.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/recap.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/repository.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/saas/subscription.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/saas/tenant.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/service.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/simulation.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/spec.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/sprint-status.yaml +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/state.template.json +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/story.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/tasks.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/test.cs +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/ui-components.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/ui-design-system.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/ui-flows.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/templates/ui-mockups.md +0 -0
- /package/{content → stacks/blazor-azure}/.morph/test-infra/example.bicep +0 -0
- /package/{content → stacks/blazor-azure}/CLAUDE.md +0 -0
- /package/{content → stacks/blazor-azure}/README.md +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack Resolver - Centralized stack path resolution for multi-stack support.
|
|
3
|
+
*
|
|
4
|
+
* Detects the active stack and resolves all stack-relative paths.
|
|
5
|
+
* Resolution order: explicit > config.json > state.json > auto-detect > fallback
|
|
6
|
+
*
|
|
7
|
+
* @module stack-resolver
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
const STACK_TYPE_TO_DIR = {
|
|
14
|
+
'blazor-server': 'blazor-azure',
|
|
15
|
+
'blazor-azure': 'blazor-azure',
|
|
16
|
+
'nextjs-supabase': 'nextjs-supabase',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Module-level override set by CLI --stack flag via setGlobalStack()
|
|
20
|
+
let _globalStackOverride = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Set a global stack override (called by CLI --stack flag).
|
|
24
|
+
* Once set, all resolveStackId() calls use this value unless
|
|
25
|
+
* an explicit parameter is provided.
|
|
26
|
+
* @param {string} stackId - Stack identifier
|
|
27
|
+
*/
|
|
28
|
+
export function setGlobalStack(stackId) {
|
|
29
|
+
_globalStackOverride = stackId;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect which stack directory to use.
|
|
34
|
+
*
|
|
35
|
+
* Resolution order:
|
|
36
|
+
* 1. explicitStack parameter (from CLI --stack flag)
|
|
37
|
+
* 2. .morph/config/config.json → project.type
|
|
38
|
+
* 3. .morph/state.json → project.type
|
|
39
|
+
* 4. Auto-detect: scan stacks/ for existing directories
|
|
40
|
+
* 5. Fallback: 'blazor-azure'
|
|
41
|
+
*
|
|
42
|
+
* @param {string} projectPath - Absolute path to project root
|
|
43
|
+
* @param {string} [explicitStack] - Override from CLI --stack flag
|
|
44
|
+
* @returns {string} Stack directory name (e.g., 'blazor-azure', 'nextjs-supabase')
|
|
45
|
+
*/
|
|
46
|
+
export function resolveStackId(projectPath, explicitStack) {
|
|
47
|
+
// 1. Explicit parameter override
|
|
48
|
+
if (explicitStack) {
|
|
49
|
+
return STACK_TYPE_TO_DIR[explicitStack] || explicitStack;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 1b. Global CLI --stack override
|
|
53
|
+
if (_globalStackOverride) {
|
|
54
|
+
return STACK_TYPE_TO_DIR[_globalStackOverride] || _globalStackOverride;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. config.json → project.type
|
|
58
|
+
try {
|
|
59
|
+
const configPath = join(projectPath, '.morph/config/config.json');
|
|
60
|
+
if (existsSync(configPath)) {
|
|
61
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
62
|
+
const projectType = config?.project?.type;
|
|
63
|
+
if (projectType && STACK_TYPE_TO_DIR[projectType]) {
|
|
64
|
+
return STACK_TYPE_TO_DIR[projectType];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch { /* continue to next source */ }
|
|
68
|
+
|
|
69
|
+
// 3. state.json → project.type
|
|
70
|
+
try {
|
|
71
|
+
const statePath = join(projectPath, '.morph/state.json');
|
|
72
|
+
if (existsSync(statePath)) {
|
|
73
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
74
|
+
const projectType = state?.project?.type;
|
|
75
|
+
if (projectType && STACK_TYPE_TO_DIR[projectType]) {
|
|
76
|
+
return STACK_TYPE_TO_DIR[projectType];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch { /* continue to next source */ }
|
|
80
|
+
|
|
81
|
+
// 4. Auto-detect: first directory in stacks/
|
|
82
|
+
try {
|
|
83
|
+
const stacksDir = join(projectPath, 'stacks');
|
|
84
|
+
if (existsSync(stacksDir)) {
|
|
85
|
+
const dirs = readdirSync(stacksDir, { withFileTypes: true })
|
|
86
|
+
.filter(d => d.isDirectory())
|
|
87
|
+
.map(d => d.name);
|
|
88
|
+
if (dirs.length === 1) {
|
|
89
|
+
return dirs[0];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch { /* continue to fallback */ }
|
|
93
|
+
|
|
94
|
+
// 5. Fallback
|
|
95
|
+
return 'blazor-azure';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get absolute path to the active stack's root directory.
|
|
100
|
+
* @param {string} projectPath
|
|
101
|
+
* @param {string} [explicitStack]
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
export function resolveStackPath(projectPath, explicitStack) {
|
|
105
|
+
return join(projectPath, 'stacks', resolveStackId(projectPath, explicitStack));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get absolute path to the active stack's agents.json.
|
|
110
|
+
* @param {string} projectPath
|
|
111
|
+
* @param {string} [explicitStack]
|
|
112
|
+
* @returns {string}
|
|
113
|
+
*/
|
|
114
|
+
export function resolveAgentsConfigPath(projectPath, explicitStack) {
|
|
115
|
+
return join(resolveStackPath(projectPath, explicitStack), '.morph/config/agents.json');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get absolute path to the active stack's standards directory.
|
|
120
|
+
* @param {string} projectPath
|
|
121
|
+
* @param {string} [explicitStack]
|
|
122
|
+
* @returns {string}
|
|
123
|
+
*/
|
|
124
|
+
export function resolveStandardsDir(projectPath, explicitStack) {
|
|
125
|
+
return join(resolveStackPath(projectPath, explicitStack), '.morph/standards');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get absolute path to the active stack's templates directory.
|
|
130
|
+
* @param {string} projectPath
|
|
131
|
+
* @param {string} [explicitStack]
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
export function resolveTemplatesDir(projectPath, explicitStack) {
|
|
135
|
+
return join(resolveStackPath(projectPath, explicitStack), '.morph/templates');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get absolute path to the active stack's config directory.
|
|
140
|
+
* @param {string} projectPath
|
|
141
|
+
* @param {string} [explicitStack]
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
export function resolveConfigDir(projectPath, explicitStack) {
|
|
145
|
+
return join(resolveStackPath(projectPath, explicitStack), '.morph/config');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { STACK_TYPE_TO_DIR };
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { readFileSync, existsSync } from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
|
+
import { resolveStandardsDir } from './stack-resolver.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Agent → Standards mapping
|
|
@@ -191,8 +192,8 @@ function loadStandard(standardName, projectPath) {
|
|
|
191
192
|
const locations = [
|
|
192
193
|
// 1. Project override
|
|
193
194
|
join(projectPath, '.morph/project/standards', `${standardName}.md`),
|
|
194
|
-
// 2.
|
|
195
|
-
join(projectPath,
|
|
195
|
+
// 2. Stack standards (resolved dynamically based on project type)
|
|
196
|
+
join(resolveStandardsDir(projectPath), `${standardName}.md`),
|
|
196
197
|
// 3. Framework standards (Blazor, CSS, .NET)
|
|
197
198
|
join(projectPath, 'framework/standards', `${standardName}.md`)
|
|
198
199
|
];
|
|
@@ -273,7 +274,7 @@ export function getStandardsListForAgent(agentId) {
|
|
|
273
274
|
export function checkStandardExists(standardName, projectPath = '.') {
|
|
274
275
|
const locations = [
|
|
275
276
|
{ type: 'project', path: join(projectPath, '.morph/project/standards', `${standardName}.md`) },
|
|
276
|
-
{ type: '
|
|
277
|
+
{ type: 'stack', path: join(resolveStandardsDir(projectPath), `${standardName}.md`) },
|
|
277
278
|
{ type: 'framework', path: join(projectPath, 'framework/standards', `${standardName}.md`) }
|
|
278
279
|
];
|
|
279
280
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import fs from 'fs/promises';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
16
|
+
import { resolveAgentsConfigPath } from './stack-resolver.js';
|
|
16
17
|
|
|
17
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
19
|
const __dirname = path.dirname(__filename);
|
|
@@ -23,7 +24,7 @@ const __dirname = path.dirname(__filename);
|
|
|
23
24
|
* @returns {Promise<Object>} Parsed agents configuration
|
|
24
25
|
*/
|
|
25
26
|
async function loadAgentsConfig(projectPath) {
|
|
26
|
-
const agentsPath =
|
|
27
|
+
const agentsPath = resolveAgentsConfigPath(projectPath);
|
|
27
28
|
const content = await fs.readFile(agentsPath, 'utf8');
|
|
28
29
|
return JSON.parse(content);
|
|
29
30
|
}
|
|
@@ -1,194 +1,204 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Troubleshoot Grep Fallback
|
|
3
|
-
* Searches markdown files for matching content when index doesn't find results
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
7
|
-
import { join, dirname, basename } from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
let
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (let i =
|
|
86
|
-
if (lines[i].startsWith('## ')) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Troubleshoot Grep Fallback
|
|
3
|
+
* Searches markdown files for matching content when index doesn't find results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
7
|
+
import { join, dirname, basename } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { resolveStandardsDir } from './stack-resolver.js';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
const frameworkRoot = join(__dirname, '..', '..');
|
|
14
|
+
|
|
15
|
+
// Directories to search (stack standards resolved dynamically)
|
|
16
|
+
const SEARCH_PATHS = [
|
|
17
|
+
'framework/standards',
|
|
18
|
+
'.wiki'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function getSearchPaths(basePath) {
|
|
22
|
+
const paths = [...SEARCH_PATHS];
|
|
23
|
+
// Insert resolved stack standards path
|
|
24
|
+
const stackStandards = resolveStandardsDir(basePath || frameworkRoot);
|
|
25
|
+
const relativePath = stackStandards.replace(basePath || frameworkRoot, '').replace(/^[\\/]/, '');
|
|
26
|
+
paths.splice(1, 0, relativePath);
|
|
27
|
+
return paths;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get all markdown files from search paths
|
|
32
|
+
* @param {string} basePath - Base path of the project
|
|
33
|
+
* @returns {string[]} Array of file paths
|
|
34
|
+
*/
|
|
35
|
+
function getMarkdownFiles(basePath) {
|
|
36
|
+
const files = [];
|
|
37
|
+
|
|
38
|
+
for (const searchPath of getSearchPaths(basePath)) {
|
|
39
|
+
const fullPath = join(basePath, searchPath);
|
|
40
|
+
|
|
41
|
+
if (!existsSync(fullPath)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const entries = readdirSync(fullPath, { withFileTypes: true });
|
|
47
|
+
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
50
|
+
files.push(join(fullPath, entry.name));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Skip directories we can't read
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extract section containing the match
|
|
63
|
+
* @param {string} content - File content
|
|
64
|
+
* @param {number} matchIndex - Index of the match
|
|
65
|
+
* @returns {Object} Section object with title and content
|
|
66
|
+
*/
|
|
67
|
+
function extractSection(content, matchIndex) {
|
|
68
|
+
const lines = content.split('\n');
|
|
69
|
+
let charCount = 0;
|
|
70
|
+
let matchLineIndex = 0;
|
|
71
|
+
|
|
72
|
+
// Find the line containing the match
|
|
73
|
+
for (let i = 0; i < lines.length; i++) {
|
|
74
|
+
charCount += lines[i].length + 1; // +1 for newline
|
|
75
|
+
if (charCount > matchIndex) {
|
|
76
|
+
matchLineIndex = i;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Find the section header (## heading) above the match
|
|
82
|
+
let sectionTitle = 'Unknown Section';
|
|
83
|
+
let sectionStart = 0;
|
|
84
|
+
|
|
85
|
+
for (let i = matchLineIndex; i >= 0; i--) {
|
|
86
|
+
if (lines[i].startsWith('## ')) {
|
|
87
|
+
sectionTitle = lines[i].replace('## ', '').trim();
|
|
88
|
+
sectionStart = i;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Find the end of the section (next ## or end of file)
|
|
94
|
+
let sectionEnd = lines.length;
|
|
95
|
+
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
96
|
+
if (lines[i].startsWith('## ')) {
|
|
97
|
+
sectionEnd = i;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Extract section content (limit to ~20 lines around match)
|
|
103
|
+
const contextStart = Math.max(sectionStart, matchLineIndex - 10);
|
|
104
|
+
const contextEnd = Math.min(sectionEnd, matchLineIndex + 10);
|
|
105
|
+
const sectionContent = lines.slice(contextStart, contextEnd).join('\n');
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
title: sectionTitle,
|
|
109
|
+
content: sectionContent,
|
|
110
|
+
lineNumber: matchLineIndex + 1
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Search a file for keywords
|
|
116
|
+
* @param {string} filePath - Path to the file
|
|
117
|
+
* @param {string[]} keywords - Keywords to search for
|
|
118
|
+
* @returns {Object[]} Array of matches
|
|
119
|
+
*/
|
|
120
|
+
function searchFile(filePath, keywords) {
|
|
121
|
+
const matches = [];
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
125
|
+
const contentLower = content.toLowerCase();
|
|
126
|
+
|
|
127
|
+
for (const keyword of keywords) {
|
|
128
|
+
const keywordLower = keyword.toLowerCase();
|
|
129
|
+
let index = contentLower.indexOf(keywordLower);
|
|
130
|
+
|
|
131
|
+
while (index !== -1) {
|
|
132
|
+
const section = extractSection(content, index);
|
|
133
|
+
|
|
134
|
+
// Check if we already have a match for this section
|
|
135
|
+
const existingMatch = matches.find(m => m.section === section.title);
|
|
136
|
+
|
|
137
|
+
if (existingMatch) {
|
|
138
|
+
existingMatch.matchCount++;
|
|
139
|
+
existingMatch.keywords.add(keyword);
|
|
140
|
+
} else {
|
|
141
|
+
matches.push({
|
|
142
|
+
file: filePath,
|
|
143
|
+
fileName: basename(filePath),
|
|
144
|
+
section: section.title,
|
|
145
|
+
content: section.content,
|
|
146
|
+
lineNumber: section.lineNumber,
|
|
147
|
+
matchCount: 1,
|
|
148
|
+
keywords: new Set([keyword])
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
index = contentLower.indexOf(keywordLower, index + 1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Skip files we can't read
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return matches;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Search all markdown files for keywords
|
|
164
|
+
* @param {string[]} keywords - Keywords to search for
|
|
165
|
+
* @param {Object} options - Search options
|
|
166
|
+
* @returns {Object[]} Array of matches sorted by relevance
|
|
167
|
+
*/
|
|
168
|
+
export function searchGrep(keywords, options = {}) {
|
|
169
|
+
// Determine base path
|
|
170
|
+
const basePath = options.basePath || join(__dirname, '../..');
|
|
171
|
+
|
|
172
|
+
const files = getMarkdownFiles(basePath);
|
|
173
|
+
let allMatches = [];
|
|
174
|
+
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const matches = searchFile(file, keywords);
|
|
177
|
+
allMatches = allMatches.concat(matches);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Convert keywords Set to Array and calculate score
|
|
181
|
+
const results = allMatches.map(match => ({
|
|
182
|
+
...match,
|
|
183
|
+
keywords: Array.from(match.keywords),
|
|
184
|
+
score: match.matchCount * 5 + match.keywords.size * 3,
|
|
185
|
+
source: 'grep'
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
// Sort by score (higher is better) and deduplicate
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
const uniqueResults = results
|
|
191
|
+
.sort((a, b) => b.score - a.score)
|
|
192
|
+
.filter(r => {
|
|
193
|
+
const key = `${r.file}:${r.section}`;
|
|
194
|
+
if (seen.has(key)) return false;
|
|
195
|
+
seen.add(key);
|
|
196
|
+
return true;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return uniqueResults.slice(0, 10); // Limit to top 10
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export default {
|
|
203
|
+
searchGrep
|
|
204
|
+
};
|