@polymorphism-tech/morph-spec 3.0.1 → 3.2.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 +561 -63
- package/LICENSE +72 -72
- package/README.md +275 -79
- package/bin/detect-agents.js +3 -1
- package/bin/morph-spec.js +60 -1
- package/bin/render-template.js +61 -14
- package/bin/semantic-detect-agents.js +2 -1
- package/bin/{task-manager.js → task-manager.cjs} +113 -8
- 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/cli-auto-detection.md +219 -0
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +3 -3
- package/docs/llm-interaction-config.md +735 -0
- package/docs/templates.md +418 -418
- package/docs/troubleshooting.md +269 -0
- package/package.json +7 -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/advance-phase.js +93 -2
- package/src/commands/analyze-blazor-concurrency.js +193 -193
- package/src/commands/approve.js +221 -0
- package/src/commands/capture-pattern.js +121 -0
- 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 +276 -149
- package/src/commands/init.js +37 -0
- package/src/commands/lint-fluent.js +352 -352
- package/src/commands/migrate-state.js +158 -0
- package/src/commands/rollback-phase.js +185 -185
- package/src/commands/search-patterns.js +126 -0
- package/src/commands/session-summary.js +291 -291
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/spawn-team.js +172 -0
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/task.js +3 -3
- package/src/commands/troubleshoot.js +222 -222
- package/src/commands/update.js +36 -0
- package/src/commands/upgrade.js +346 -0
- 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/generator/.gitkeep +0 -0
- package/src/generator/config-generator.js +206 -0
- package/src/generator/templates/config.json.template +40 -0
- package/src/generator/templates/project.md.template +67 -0
- 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/checkpoint-hooks.js +258 -0
- 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/metadata-extractor.js +380 -0
- package/src/lib/mockup-generator.js +366 -366
- package/src/lib/phase-state-machine.js +214 -0
- package/src/lib/stack-resolver.js +148 -0
- package/src/lib/standards-context-injector.js +4 -3
- package/src/lib/state-manager.js +120 -0
- package/src/lib/team-orchestrator.js +2 -1
- package/src/lib/template-data-sources.js +325 -0
- 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/content-validator.js +351 -0
- package/src/lib/validators/package-validator.js +360 -360
- package/src/lib/validators/ui-contrast-validator.js +422 -422
- package/src/llm/.gitkeep +0 -0
- package/src/llm/analyzer.js +215 -0
- package/src/llm/environment-detector.js +43 -0
- package/src/llm/few-shot-examples.js +216 -0
- package/src/llm/project-config-schema.json +188 -0
- package/src/llm/prompt-builder.js +96 -0
- package/src/llm/schema-validator.js +121 -0
- package/src/orchestrator.js +206 -0
- package/src/sanitizer/.gitkeep +0 -0
- package/src/sanitizer/context-sanitizer.js +221 -0
- package/src/sanitizer/patterns.js +163 -0
- package/src/scanner/.gitkeep +0 -0
- package/src/scanner/project-scanner.js +242 -0
- package/src/types/index.js +477 -0
- package/src/ui/.gitkeep +0 -0
- package/src/ui/diff-display.js +91 -0
- package/src/ui/interactive-wizard.js +96 -0
- package/src/ui/user-review.js +211 -0
- package/src/ui/wizard-questions.js +190 -0
- package/src/utils/file-copier.js +3 -1
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/src/writer/.gitkeep +0 -0
- package/src/writer/file-writer.js +86 -0
- 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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Prompt Builder - Builds LLM prompt with few-shot examples
|
|
3
|
+
* @module morph-spec/llm/prompt-builder
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getFewShotExamples } from './few-shot-examples.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('../types/index.js').SanitizedContext} SanitizedContext
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build structured prompt for LLM with few-shot examples
|
|
14
|
+
* @param {SanitizedContext} context - Sanitized project context
|
|
15
|
+
* @returns {string} Formatted prompt
|
|
16
|
+
*/
|
|
17
|
+
export function buildPrompt(context) {
|
|
18
|
+
const examples = getFewShotExamples();
|
|
19
|
+
|
|
20
|
+
return `You are a project analysis AI. Analyze the following project context and return a JSON configuration.
|
|
21
|
+
|
|
22
|
+
## Task
|
|
23
|
+
Analyze the project structure, dependencies, and files to determine:
|
|
24
|
+
- Project name, description, and type
|
|
25
|
+
- Technology stack (frontend, backend, database, hosting)
|
|
26
|
+
- Architecture pattern
|
|
27
|
+
- Project structure description
|
|
28
|
+
- Code conventions
|
|
29
|
+
|
|
30
|
+
Return ONLY valid JSON matching the schema below. Do not include any explanatory text outside the JSON.
|
|
31
|
+
|
|
32
|
+
## Few-Shot Examples
|
|
33
|
+
|
|
34
|
+
${examples.map((ex, i) => `
|
|
35
|
+
### Example ${i + 1}
|
|
36
|
+
|
|
37
|
+
**Input:**
|
|
38
|
+
\`\`\`json
|
|
39
|
+
${JSON.stringify(ex.input, null, 2)}
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
**Output:**
|
|
43
|
+
\`\`\`json
|
|
44
|
+
${JSON.stringify(ex.output, null, 2)}
|
|
45
|
+
\`\`\`
|
|
46
|
+
`).join('\n')}
|
|
47
|
+
|
|
48
|
+
## Project Context to Analyze
|
|
49
|
+
|
|
50
|
+
\`\`\`json
|
|
51
|
+
${JSON.stringify(context, null, 2)}
|
|
52
|
+
\`\`\`
|
|
53
|
+
|
|
54
|
+
## JSON Schema (Output Format)
|
|
55
|
+
|
|
56
|
+
\`\`\`json
|
|
57
|
+
{
|
|
58
|
+
"name": "string (project name)",
|
|
59
|
+
"description": "string (1-2 sentences)",
|
|
60
|
+
"type": "monorepo | blazor-server | nextjs | cli-tool | dotnet-api | other",
|
|
61
|
+
"stack": {
|
|
62
|
+
"frontend": { "tech": "string", "version": "string", "details": "string" } | null,
|
|
63
|
+
"backend": { "tech": "string", "version": "string", "details": "string" },
|
|
64
|
+
"database": { "tech": "string", "version": "string", "details": "string" } | null,
|
|
65
|
+
"hosting": "string | null"
|
|
66
|
+
},
|
|
67
|
+
"architecture": "clean-architecture | monolith | microservices | layered | other",
|
|
68
|
+
"projectStructure": "string (describe folder structure)",
|
|
69
|
+
"conventions": "string (coding conventions detected)",
|
|
70
|
+
"repository": "string | null",
|
|
71
|
+
"hasAzure": boolean,
|
|
72
|
+
"hasDocker": boolean,
|
|
73
|
+
"hasDevOps": boolean,
|
|
74
|
+
"confidence": number (0-100),
|
|
75
|
+
"warnings": ["string"] (uncertainties or issues)
|
|
76
|
+
}
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
## Rules
|
|
80
|
+
1. Return ONLY the JSON object, no additional text
|
|
81
|
+
2. Infer missing information from context (package.json, .csproj files, directory structure)
|
|
82
|
+
3. Set \`confidence\` based on how clear the project structure is (90-100 = very clear, 70-89 = moderate, <70 = unclear)
|
|
83
|
+
4. Add \`warnings\` for any uncertainties or missing critical information
|
|
84
|
+
5. If no frontend is detected, set \`stack.frontend\` to null
|
|
85
|
+
6. If no database is detected, set \`stack.database\` to null
|
|
86
|
+
7. Detect project type based on:
|
|
87
|
+
- \`nextjs\`: package.json has "next" dependency
|
|
88
|
+
- \`blazor-server\`: .csproj files with Blazor.Server references
|
|
89
|
+
- \`dotnet-api\`: .csproj files without Blazor
|
|
90
|
+
- \`cli-tool\`: package.json with bin field or .csproj with OutputType=Exe
|
|
91
|
+
- \`monorepo\`: multiple top-level directories like frontend/, backend/, packages/
|
|
92
|
+
- \`other\`: if none of the above match
|
|
93
|
+
|
|
94
|
+
## Response
|
|
95
|
+
Return the JSON analysis now:`;
|
|
96
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Schema Validator - Validates LLM JSON responses
|
|
3
|
+
* @module morph-spec/llm/schema-validator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Ajv from 'ajv';
|
|
7
|
+
import addFormats from 'ajv-formats';
|
|
8
|
+
import projectConfigSchema from './project-config-schema.json' with { type: 'json' };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validation Error with detailed information
|
|
12
|
+
*/
|
|
13
|
+
export class ValidationError extends Error {
|
|
14
|
+
constructor(message, field, value, errors = []) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'ValidationError';
|
|
17
|
+
this.field = field;
|
|
18
|
+
this.value = value;
|
|
19
|
+
this.errors = errors;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SchemaValidator - Validates JSON responses against schema
|
|
25
|
+
* @class
|
|
26
|
+
*/
|
|
27
|
+
export class SchemaValidator {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.ajv = new Ajv({
|
|
30
|
+
allErrors: true,
|
|
31
|
+
verbose: true,
|
|
32
|
+
strict: false
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Add format validators (uri, email, etc.)
|
|
36
|
+
addFormats(this.ajv);
|
|
37
|
+
|
|
38
|
+
// Compile schema
|
|
39
|
+
this.validateProjectConfig = this.ajv.compile(projectConfigSchema);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate ProjectConfig JSON
|
|
44
|
+
* @param {Object|string} data - JSON object or string
|
|
45
|
+
* @returns {Object} Validated object
|
|
46
|
+
* @throws {ValidationError} If validation fails
|
|
47
|
+
*/
|
|
48
|
+
validate(data) {
|
|
49
|
+
// Parse if string
|
|
50
|
+
let parsed = data;
|
|
51
|
+
if (typeof data === 'string') {
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(data);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new ValidationError(
|
|
56
|
+
`Invalid JSON: ${error.message}`,
|
|
57
|
+
'json',
|
|
58
|
+
data,
|
|
59
|
+
[]
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate against schema
|
|
65
|
+
const valid = this.validateProjectConfig(parsed);
|
|
66
|
+
|
|
67
|
+
if (!valid) {
|
|
68
|
+
const errors = this.validateProjectConfig.errors || [];
|
|
69
|
+
const errorMessages = errors
|
|
70
|
+
.map(err => {
|
|
71
|
+
const path = err.instancePath || err.dataPath || 'root';
|
|
72
|
+
const message = err.message || 'unknown error';
|
|
73
|
+
return `${path}: ${message}`;
|
|
74
|
+
})
|
|
75
|
+
.join('; ');
|
|
76
|
+
|
|
77
|
+
throw new ValidationError(
|
|
78
|
+
`Schema validation failed: ${errorMessages}`,
|
|
79
|
+
'schema',
|
|
80
|
+
parsed,
|
|
81
|
+
errors
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate with fallback - returns null instead of throwing
|
|
90
|
+
* @param {Object|string} data - JSON object or string
|
|
91
|
+
* @returns {Object|null} Validated object or null if invalid
|
|
92
|
+
*/
|
|
93
|
+
validateSafe(data) {
|
|
94
|
+
try {
|
|
95
|
+
return this.validate(data);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get human-readable error messages
|
|
103
|
+
* @param {Object|string} data - JSON object or string
|
|
104
|
+
* @returns {string[]} Array of error messages
|
|
105
|
+
*/
|
|
106
|
+
getErrors(data) {
|
|
107
|
+
try {
|
|
108
|
+
this.validate(data);
|
|
109
|
+
return [];
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error instanceof ValidationError) {
|
|
112
|
+
return error.errors.map(err => {
|
|
113
|
+
const path = err.instancePath || err.dataPath || 'root';
|
|
114
|
+
const message = err.message || 'unknown error';
|
|
115
|
+
return `${path}: ${message}`;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return [error.message];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview AutoContextOrchestrator - Main orchestrator for CLI auto-detection
|
|
3
|
+
* @module morph-spec/orchestrator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { ProjectScanner } from './scanner/project-scanner.js';
|
|
8
|
+
import { ContextSanitizer } from './sanitizer/context-sanitizer.js';
|
|
9
|
+
import { LLMAnalyzer } from './llm/analyzer.js';
|
|
10
|
+
import { ConfigGenerator } from './generator/config-generator.js';
|
|
11
|
+
import { UserReview } from './ui/user-review.js';
|
|
12
|
+
import { InteractiveWizard } from './ui/interactive-wizard.js';
|
|
13
|
+
import { FileWriter } from './writer/file-writer.js';
|
|
14
|
+
import { readFile, access } from 'fs/promises';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {import('./types/index.js').GeneratedConfigs} GeneratedConfigs
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* AutoContextOrchestrator - Orchestrates the complete auto-detection flow
|
|
23
|
+
* @class
|
|
24
|
+
*/
|
|
25
|
+
export class AutoContextOrchestrator {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.scanner = new ProjectScanner();
|
|
28
|
+
this.sanitizer = new ContextSanitizer();
|
|
29
|
+
this.llmAnalyzer = new LLMAnalyzer();
|
|
30
|
+
this.configGenerator = new ConfigGenerator();
|
|
31
|
+
this.userReview = new UserReview();
|
|
32
|
+
this.wizard = new InteractiveWizard();
|
|
33
|
+
this.fileWriter = new FileWriter();
|
|
34
|
+
|
|
35
|
+
// Handle Ctrl+C gracefully
|
|
36
|
+
this.setupSignalHandlers();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute the complete auto-detection flow
|
|
41
|
+
* @param {string} cwd - Current working directory
|
|
42
|
+
* @param {Object} [options] - Orchestration options
|
|
43
|
+
* @param {boolean} [options.skipReview] - Skip user review (auto-approve)
|
|
44
|
+
* @param {boolean} [options.fallbackOnError] - Fallback to wizard on LLM error (default: true)
|
|
45
|
+
* @param {boolean} [options.wizardMode] - Force wizard mode (skip LLM)
|
|
46
|
+
* @returns {Promise<{success: boolean, configs: GeneratedConfigs|null}>}
|
|
47
|
+
*/
|
|
48
|
+
async execute(cwd, options = {}) {
|
|
49
|
+
const {
|
|
50
|
+
skipReview = false,
|
|
51
|
+
fallbackOnError = true,
|
|
52
|
+
wizardMode = false
|
|
53
|
+
} = options;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
console.log(chalk.bold.cyan('\n🔍 MORPH-SPEC Auto Context Detection\n'));
|
|
57
|
+
|
|
58
|
+
let projectConfig;
|
|
59
|
+
|
|
60
|
+
// Step 1: Decide between LLM or Wizard mode
|
|
61
|
+
if (wizardMode) {
|
|
62
|
+
console.log(chalk.yellow(' Using interactive wizard mode (--wizard flag)\n'));
|
|
63
|
+
projectConfig = await this.wizard.run();
|
|
64
|
+
} else {
|
|
65
|
+
try {
|
|
66
|
+
// Step 1a: Scan project
|
|
67
|
+
console.log(chalk.dim(' [1/6] Scanning project directory...'));
|
|
68
|
+
const projectContext = await this.scanner.scan(cwd);
|
|
69
|
+
|
|
70
|
+
// Step 1b: Sanitize context
|
|
71
|
+
console.log(chalk.dim(' [2/6] Sanitizing context (removing secrets)...'));
|
|
72
|
+
const sanitizedContext = this.sanitizer.sanitize(projectContext);
|
|
73
|
+
|
|
74
|
+
// Step 1c: Analyze with LLM
|
|
75
|
+
console.log(chalk.dim(' [3/6] Analyzing with Claude Code LLM...'));
|
|
76
|
+
projectConfig = await this.llmAnalyzer.analyze(sanitizedContext);
|
|
77
|
+
|
|
78
|
+
console.log(chalk.green(' ✓ Auto-detection successful\n'));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// LLM failed
|
|
81
|
+
console.log(chalk.yellow(`\n ⚠️ Auto-detection failed: ${error.message}\n`));
|
|
82
|
+
|
|
83
|
+
if (!fallbackOnError) {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fallback to wizard
|
|
88
|
+
console.log(chalk.cyan(' Falling back to interactive wizard...\n'));
|
|
89
|
+
projectConfig = await this.wizard.run();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Step 2: Generate configs
|
|
94
|
+
console.log(chalk.dim(' [4/6] Generating configuration files...'));
|
|
95
|
+
const configs = await this.configGenerator.generate(projectConfig);
|
|
96
|
+
|
|
97
|
+
// Step 3: User review (unless skipped)
|
|
98
|
+
let finalConfigs = configs;
|
|
99
|
+
|
|
100
|
+
if (!skipReview) {
|
|
101
|
+
console.log(chalk.dim(' [5/6] Requesting user approval...'));
|
|
102
|
+
|
|
103
|
+
// Check if updating existing configs
|
|
104
|
+
const existingConfigs = await this.readExistingConfigs(cwd);
|
|
105
|
+
|
|
106
|
+
const approvalResponse = await this.userReview.promptForApproval(
|
|
107
|
+
configs,
|
|
108
|
+
projectConfig,
|
|
109
|
+
existingConfigs
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (approvalResponse.action === 'cancel') {
|
|
113
|
+
console.log(chalk.yellow('\n❌ Operation canceled by user\n'));
|
|
114
|
+
console.log(chalk.dim(` Reason: ${approvalResponse.cancelReason || 'User canceled'}\n`));
|
|
115
|
+
return { success: false, configs: null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (approvalResponse.editedConfigs) {
|
|
119
|
+
finalConfigs = approvalResponse.editedConfigs;
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
console.log(chalk.dim(' [5/6] Skipping user review (auto-approve)'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Step 4: Save configs
|
|
126
|
+
console.log(chalk.dim(' [6/6] Saving configuration files...'));
|
|
127
|
+
|
|
128
|
+
// Backup existing configs if they exist
|
|
129
|
+
await this.configGenerator.backupExisting(cwd);
|
|
130
|
+
|
|
131
|
+
// Write new configs
|
|
132
|
+
await this.fileWriter.save(cwd, finalConfigs);
|
|
133
|
+
|
|
134
|
+
console.log(chalk.bold.green('🎉 Auto-detection complete!\n'));
|
|
135
|
+
|
|
136
|
+
return { success: true, configs: finalConfigs };
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.log(chalk.bold.red('\n❌ Auto-detection failed\n'));
|
|
139
|
+
console.log(chalk.red(` Error: ${error.message}\n`));
|
|
140
|
+
|
|
141
|
+
if (error.stack && process.env.DEBUG) {
|
|
142
|
+
console.log(chalk.dim(error.stack));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { success: false, configs: null };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Read existing configs (if they exist)
|
|
151
|
+
* @param {string} cwd - Current working directory
|
|
152
|
+
* @returns {Promise<Object|null>} Existing configs or null
|
|
153
|
+
*/
|
|
154
|
+
async readExistingConfigs(cwd) {
|
|
155
|
+
try {
|
|
156
|
+
const projectMdPath = join(cwd, '.morph', 'project.md');
|
|
157
|
+
const configJsonPath = join(cwd, '.morph', 'config', 'config.json');
|
|
158
|
+
|
|
159
|
+
const [projectMdExists, configJsonExists] = await Promise.all([
|
|
160
|
+
this.fileExists(projectMdPath),
|
|
161
|
+
this.fileExists(configJsonPath)
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
if (!projectMdExists && !configJsonExists) {
|
|
165
|
+
return null; // No existing configs
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const [projectMd, configJson] = await Promise.all([
|
|
169
|
+
projectMdExists ? readFile(projectMdPath, 'utf-8') : null,
|
|
170
|
+
configJsonExists ? readFile(configJsonPath, 'utf-8') : null
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
return { projectMd, configJson };
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return null; // Error reading configs, treat as non-existent
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check if file exists
|
|
181
|
+
* @param {string} filepath - File path
|
|
182
|
+
* @returns {Promise<boolean>}
|
|
183
|
+
*/
|
|
184
|
+
async fileExists(filepath) {
|
|
185
|
+
try {
|
|
186
|
+
await access(filepath);
|
|
187
|
+
return true;
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Setup signal handlers for graceful shutdown
|
|
195
|
+
*/
|
|
196
|
+
setupSignalHandlers() {
|
|
197
|
+
const handleExit = () => {
|
|
198
|
+
console.log(chalk.yellow('\n\n⚠️ Operation interrupted by user (Ctrl+C)\n'));
|
|
199
|
+
console.log(chalk.dim(' No files were modified\n'));
|
|
200
|
+
process.exit(0);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
process.on('SIGINT', handleExit);
|
|
204
|
+
process.on('SIGTERM', handleExit);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview ContextSanitizer - Sanitizes project context before sending to LLM
|
|
3
|
+
* @module morph-spec/sanitizer/context-sanitizer
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { minimatch } from 'minimatch';
|
|
7
|
+
import {
|
|
8
|
+
WHITELIST_FILES,
|
|
9
|
+
BLACKLIST_DIRECTORIES,
|
|
10
|
+
BLACKLIST_FILES,
|
|
11
|
+
SECRET_PATTERNS,
|
|
12
|
+
MAX_FILE_SIZE,
|
|
13
|
+
MAX_SUMMARY_LENGTH
|
|
14
|
+
} from './patterns.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {import('../types/index.js').ProjectContext} ProjectContext
|
|
18
|
+
* @typedef {import('../types/index.js').SanitizedContext} SanitizedContext
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ContextSanitizer - Removes secrets and sensitive data before LLM analysis
|
|
23
|
+
* Implements hybrid whitelist/blacklist approach per ADR-004
|
|
24
|
+
* @class
|
|
25
|
+
*/
|
|
26
|
+
export class ContextSanitizer {
|
|
27
|
+
/**
|
|
28
|
+
* Sanitize project context (remove secrets, truncate files)
|
|
29
|
+
* @param {ProjectContext} context - Raw project context
|
|
30
|
+
* @returns {SanitizedContext}
|
|
31
|
+
*/
|
|
32
|
+
sanitize(context) {
|
|
33
|
+
// Sanitize package.json (remove private fields, redact secrets)
|
|
34
|
+
const sanitizedPackageJson = this.sanitizePackageJson(context.packageJson);
|
|
35
|
+
|
|
36
|
+
// Extract only filenames from .csproj paths (not full content)
|
|
37
|
+
const csprojFilenames = context.csprojFiles.map(path => path.split('/').pop());
|
|
38
|
+
|
|
39
|
+
// Truncate README and CLAUDE.md to first 500 chars
|
|
40
|
+
const readmeSummary = context.readme
|
|
41
|
+
? this.truncateText(context.readme, MAX_SUMMARY_LENGTH)
|
|
42
|
+
: null;
|
|
43
|
+
|
|
44
|
+
const claudeMdSummary = context.claudeMd
|
|
45
|
+
? this.truncateText(context.claudeMd, MAX_SUMMARY_LENGTH)
|
|
46
|
+
: null;
|
|
47
|
+
|
|
48
|
+
// Sanitize infrastructure summary (remove secrets, just count files)
|
|
49
|
+
const infraSummary = {
|
|
50
|
+
dockerfilesCount: context.infraFiles.dockerfiles.length,
|
|
51
|
+
dockerComposeCount: context.infraFiles.dockerComposeFiles.length,
|
|
52
|
+
bicepFilesCount: context.infraFiles.bicepFiles.length,
|
|
53
|
+
pipelinesCount: context.infraFiles.pipelines.length,
|
|
54
|
+
hasAzure: context.infraFiles.hasAzure,
|
|
55
|
+
hasDocker: context.infraFiles.hasDocker,
|
|
56
|
+
hasDevOps: context.infraFiles.hasDevOps
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Extract git remote domain only (not full URL with credentials)
|
|
60
|
+
const gitRemoteDomain = context.gitRemote
|
|
61
|
+
? this.extractDomain(context.gitRemote)
|
|
62
|
+
: null;
|
|
63
|
+
|
|
64
|
+
// Estimate total files (approximate)
|
|
65
|
+
const totalFiles = context.structure.topLevelDirs.length * 10; // rough estimate
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
packageJson: sanitizedPackageJson,
|
|
69
|
+
csprojFilenames,
|
|
70
|
+
hasSolution: context.solutionFile !== null,
|
|
71
|
+
readmeSummary,
|
|
72
|
+
claudeMdSummary,
|
|
73
|
+
structure: context.structure,
|
|
74
|
+
infraSummary,
|
|
75
|
+
gitRemoteDomain,
|
|
76
|
+
totalFiles
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Sanitize package.json - remove private fields and redact secrets
|
|
82
|
+
* @param {Object|null} packageJson - Raw package.json
|
|
83
|
+
* @returns {Object}
|
|
84
|
+
*/
|
|
85
|
+
sanitizePackageJson(packageJson) {
|
|
86
|
+
if (!packageJson) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Keep only safe fields
|
|
91
|
+
const safe = {
|
|
92
|
+
name: packageJson.name || 'unknown',
|
|
93
|
+
version: packageJson.version,
|
|
94
|
+
description: packageJson.description,
|
|
95
|
+
type: packageJson.type,
|
|
96
|
+
engines: packageJson.engines
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Include dependencies (names only, no versions for simplicity)
|
|
100
|
+
if (packageJson.dependencies) {
|
|
101
|
+
safe.dependencies = Object.keys(packageJson.dependencies);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (packageJson.devDependencies) {
|
|
105
|
+
safe.devDependencies = Object.keys(packageJson.devDependencies);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Include scripts (but redact any that might contain secrets)
|
|
109
|
+
if (packageJson.scripts) {
|
|
110
|
+
safe.scripts = {};
|
|
111
|
+
for (const [key, value] of Object.entries(packageJson.scripts)) {
|
|
112
|
+
safe.scripts[key] = this.redactSecrets(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return safe;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Detect and redact secrets from text
|
|
121
|
+
* @param {string} text - Text to sanitize
|
|
122
|
+
* @returns {string} Sanitized text
|
|
123
|
+
*/
|
|
124
|
+
redactSecrets(text) {
|
|
125
|
+
if (!text || typeof text !== 'string') {
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let sanitized = text;
|
|
130
|
+
|
|
131
|
+
// Apply all secret patterns
|
|
132
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
133
|
+
sanitized = sanitized.replace(pattern.regex, pattern.replacement);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return sanitized;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if file should be excluded from LLM analysis
|
|
141
|
+
* @param {string} filepath - File path (relative or absolute)
|
|
142
|
+
* @returns {boolean} True if file should be excluded
|
|
143
|
+
*/
|
|
144
|
+
shouldExcludeFile(filepath) {
|
|
145
|
+
const normalizedPath = filepath.replace(/\\/g, '/');
|
|
146
|
+
|
|
147
|
+
// Check if path contains blacklisted directory
|
|
148
|
+
for (const blacklistDir of BLACKLIST_DIRECTORIES) {
|
|
149
|
+
if (normalizedPath.includes(`/${blacklistDir}/`) || normalizedPath.startsWith(`${blacklistDir}/`)) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check if filename matches blacklist pattern
|
|
155
|
+
const filename = normalizedPath.split('/').pop();
|
|
156
|
+
for (const pattern of BLACKLIST_FILES) {
|
|
157
|
+
if (minimatch(filename, pattern, { nocase: true })) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if file is whitelisted for reading
|
|
167
|
+
* @param {string} filepath - File path
|
|
168
|
+
* @returns {boolean} True if file is whitelisted
|
|
169
|
+
*/
|
|
170
|
+
isWhitelisted(filepath) {
|
|
171
|
+
const normalizedPath = filepath.replace(/\\/g, '/');
|
|
172
|
+
const filename = normalizedPath.split('/').pop();
|
|
173
|
+
|
|
174
|
+
for (const pattern of WHITELIST_FILES) {
|
|
175
|
+
if (minimatch(filename, pattern, { nocase: true })) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
// Also check full path for patterns like ".github/workflows/*.yml"
|
|
179
|
+
if (minimatch(normalizedPath, pattern, { nocase: true })) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Truncate text to maximum length
|
|
189
|
+
* @param {string} text - Text to truncate
|
|
190
|
+
* @param {number} maxLength - Maximum length
|
|
191
|
+
* @returns {string} Truncated text
|
|
192
|
+
*/
|
|
193
|
+
truncateText(text, maxLength) {
|
|
194
|
+
if (!text || text.length <= maxLength) {
|
|
195
|
+
return text;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return text.substring(0, maxLength) + '... [truncated]';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Extract domain from git remote URL
|
|
203
|
+
* @param {string} url - Git remote URL
|
|
204
|
+
* @returns {string|null} Domain only (e.g., "github.com")
|
|
205
|
+
*/
|
|
206
|
+
extractDomain(url) {
|
|
207
|
+
try {
|
|
208
|
+
// Handle SSH format: git@github.com:user/repo.git
|
|
209
|
+
if (url.startsWith('git@')) {
|
|
210
|
+
const match = url.match(/git@([^:]+):/);
|
|
211
|
+
return match ? match[1] : null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Handle HTTPS format: https://github.com/user/repo.git
|
|
215
|
+
const urlObj = new URL(url);
|
|
216
|
+
return urlObj.hostname;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|