@polymorphism-tech/morph-spec 3.1.0 → 4.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 +882 -3
- package/README.md +79 -18
- package/bin/detect-agents.js +1 -1
- package/bin/morph-spec.js +163 -26
- package/bin/task-manager.cjs +101 -7
- package/bin/validate.js +1 -1
- package/docs/cli-auto-detection.md +219 -0
- package/docs/getting-started.md +0 -5
- package/docs/llm-interaction-config.md +735 -0
- package/docs/troubleshooting.md +269 -0
- package/docs/v3.0/AGENTS.md +521 -0
- package/docs/v3.0/ANALYSIS.md +555 -0
- package/docs/v3.0/ARCHITECTURE.md +436 -0
- package/docs/v3.0/EXECUTION-FLOW.md +1304 -0
- package/docs/v3.0/FEATURES.md +688 -0
- package/docs/v3.0/README.md +231 -0
- package/docs/v3.0/ROADMAP.md +801 -0
- package/docs/validation-checklist.md +0 -1
- package/package.json +5 -1
- package/src/commands/agents/index.js +4 -0
- package/src/commands/agents/spawn-team.js +172 -0
- package/src/commands/{create-story.js → feature/create-story.js} +357 -354
- package/src/commands/feature/index.js +6 -0
- package/src/commands/{shard-spec.js → feature/shard-spec.js} +2 -2
- package/src/commands/{sprint-status.js → feature/sprint-status.js} +1 -1
- package/src/commands/{generate-context.js → generation/generate-context.js} +40 -40
- package/src/commands/{generate.js → generation/generate.js} +130 -3
- package/src/commands/generation/index.js +5 -0
- package/src/commands/index.js +16 -0
- package/src/commands/learning/capture-pattern.js +121 -0
- package/src/commands/learning/index.js +5 -0
- package/src/commands/learning/search-patterns.js +126 -0
- package/src/commands/{detect-agents.js → project/detect-agents.js} +178 -178
- package/src/commands/project/detect-workflow.js +174 -0
- package/src/commands/{detect.js → project/detect.js} +104 -104
- package/src/commands/{doctor.js → project/doctor.js} +356 -356
- package/src/commands/project/index.js +10 -0
- package/src/commands/{init.js → project/init.js} +305 -258
- package/src/commands/{sync.js → project/sync.js} +167 -167
- package/src/commands/{update.js → project/update.js} +240 -204
- package/src/commands/{advance-phase.js → state/advance-phase.js} +416 -266
- package/src/commands/state/approve.js +221 -0
- package/src/commands/state/index.js +8 -0
- package/src/commands/{rollback-phase.js → state/rollback-phase.js} +185 -185
- package/src/commands/{state.js → state/state.js} +334 -334
- package/src/commands/{validate-phase.js → state/validate-phase.js} +221 -221
- package/src/commands/tasks/index.js +4 -0
- package/src/commands/{task.js → tasks/task.js} +78 -78
- package/src/commands/templates/index.js +8 -0
- package/src/commands/templates/template-customize.js +101 -0
- package/src/commands/templates/template-list.js +128 -0
- package/src/commands/templates/template-render.js +95 -0
- package/src/commands/templates/template-show.js +131 -0
- package/src/commands/templates/template-validate.js +91 -0
- package/src/commands/utils/index.js +7 -0
- package/src/commands/utils/migrate-state.js +158 -0
- package/src/commands/{session-summary.js → utils/session-summary.js} +291 -291
- package/src/commands/{troubleshoot.js → utils/troubleshoot.js} +222 -222
- package/src/commands/utils/upgrade.js +346 -0
- package/src/commands/{analyze-blazor-concurrency.js → validation/analyze-blazor-concurrency.js} +193 -193
- package/src/commands/validation/index.js +8 -0
- package/src/commands/{lint-fluent.js → validation/lint-fluent.js} +352 -352
- package/src/commands/{validate-blazor-state.js → validation/validate-blazor-state.js} +210 -210
- package/src/commands/{validate-blazor.js → validation/validate-blazor.js} +156 -156
- package/src/commands/{validate-css.js → validation/validate-css.js} +84 -84
- package/src/core/index.js +10 -0
- package/src/core/registry/command-registry.js +302 -0
- package/src/core/registry/index.js +8 -0
- package/src/core/registry/validator-registry.js +204 -0
- package/src/core/state/index.js +8 -0
- package/src/core/state/phase-state-machine.js +214 -0
- package/src/{lib → core/state}/state-manager.js +572 -414
- package/src/core/templates/index.js +9 -0
- package/src/core/templates/template-data-sources.js +325 -0
- package/src/core/templates/template-registry.js +335 -0
- package/src/core/templates/template-renderer.js +477 -0
- package/src/core/templates/template-validator.js +296 -0
- package/src/core/workflows/index.js +7 -0
- package/src/core/workflows/workflow-detector.js +354 -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/{complexity-analyzer.js → analysis/complexity-analyzer.js} +441 -441
- package/src/lib/analysis/index.js +7 -0
- package/src/lib/checkpoints/checkpoint-hooks.js +258 -0
- package/src/lib/checkpoints/index.js +7 -0
- package/src/lib/detectors/config-detector.js +223 -223
- package/src/lib/detectors/conversation-analyzer.js +163 -163
- package/src/lib/{design-system-detector.js → detectors/design-system-detector.js} +187 -187
- package/src/lib/detectors/index.js +87 -84
- package/src/lib/detectors/standards-generator.js +275 -275
- package/src/lib/detectors/structure-detector.js +245 -245
- package/src/lib/{context-generator.js → generators/context-generator.js} +526 -516
- package/src/lib/generators/index.js +10 -0
- package/src/lib/generators/metadata-extractor.js +387 -0
- package/src/lib/{recap-generator.js → generators/recap-generator.js} +205 -205
- package/src/lib/learning/index.js +7 -0
- package/src/lib/orchestration/index.js +7 -0
- package/src/lib/{team-orchestrator.js → orchestration/team-orchestrator.js} +323 -323
- package/src/lib/stacks/index.js +7 -0
- package/src/lib/{stack-resolver.js → stacks/stack-resolver.js} +180 -148
- package/src/lib/standards/index.js +7 -0
- package/src/lib/{standards-context-injector.js → standards/standards-context-injector.js} +298 -288
- package/src/lib/troubleshooting/index.js +8 -0
- package/src/lib/{troubleshoot-grep.js → troubleshooting/troubleshoot-grep.js} +204 -204
- package/src/lib/{troubleshoot-index.js → troubleshooting/troubleshoot-index.js} +144 -144
- package/src/lib/validators/architecture/architecture-validator.js +387 -0
- package/src/lib/validators/architecture/index.js +7 -0
- package/src/lib/validators/architecture-validator.js +40 -367
- package/src/lib/{blazor-concurrency-analyzer.js → validators/blazor/blazor-concurrency-analyzer.js} +277 -288
- package/src/lib/{blazor-state-validator.js → validators/blazor/blazor-state-validator.js} +279 -291
- package/src/lib/{blazor-validator.js → validators/blazor/blazor-validator.js} +369 -374
- package/src/lib/validators/blazor/index.js +9 -0
- package/src/lib/validators/content/content-validator.js +351 -0
- package/src/lib/validators/content/index.js +7 -0
- package/src/lib/validators/content-validator.js +164 -0
- package/src/lib/validators/{contract-compliance-validator.js → contracts/contract-compliance-validator.js} +273 -273
- package/src/lib/validators/contracts/index.js +7 -0
- package/src/lib/{css-validator.js → validators/css/css-validator.js} +352 -352
- package/src/lib/validators/css/index.js +7 -0
- package/src/lib/validators/{design-system-validator.js → design-system/design-system-validator.js} +231 -231
- package/src/lib/validators/design-system/index.js +7 -0
- package/src/lib/validators/package-validator.js +41 -340
- package/src/lib/validators/packages/index.js +7 -0
- package/src/lib/validators/packages/package-validator.js +360 -0
- package/src/lib/validators/shared/index.js +12 -0
- package/src/lib/validators/shared/issue-counter.js +18 -0
- package/src/lib/validators/shared/result-formatter.js +124 -0
- package/src/lib/{spec-validator.js → validators/spec-validator.js} +258 -258
- package/src/lib/validators/ui/index.js +7 -0
- package/src/lib/validators/ui/ui-contrast-validator.js +422 -0
- package/src/lib/validators/ui-contrast-validator.js +31 -409
- package/src/lib/{validation-runner.js → validators/validation-runner.js} +286 -284
- 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/orchestrator.js +206 -0
- package/src/sanitizer/context-sanitizer.js +221 -0
- package/src/sanitizer/patterns.js +163 -0
- package/src/scanner/project-scanner.js +242 -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 +188 -0
- package/src/utils/color-utils.js +70 -0
- package/src/utils/file-copier.js +188 -189
- package/src/utils/process-handler.js +97 -0
- package/src/writer/file-writer.js +86 -0
- package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +3 -3
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +59 -0
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +45 -255
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +33 -88
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +25 -89
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +64 -0
- package/stacks/blazor-azure/.morph/config/agents.json +879 -764
- package/stacks/blazor-azure/.morph/hooks/{pre-commit-tests.sh → pre-commit/tests-csharp.sh} +3 -2
- package/stacks/blazor-azure/.morph/templates/.gitkeep +0 -0
- package/stacks/blazor-azure/.morph/templates/infrastructure/github/workflows/cd-prod.yml.hbs +41 -0
- package/stacks/blazor-azure/.morph/templates/infrastructure/github/workflows/cd-staging.yml.hbs +24 -0
- package/stacks/blazor-azure/.morph/templates/infrastructure/github/workflows/ci-build.yml.hbs +23 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +221 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +79 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +529 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +209 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +227 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +122 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-status.md +86 -0
- package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +122 -0
- package/stacks/nextjs-supabase/.claude/settings.local.json +6 -0
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +30 -150
- package/stacks/nextjs-supabase/.morph/config/agents.json +345 -345
- package/stacks/nextjs-supabase/.morph/hooks/pre-commit/tests-typescript.sh +61 -0
- package/stacks/nextjs-supabase/.morph/templates/.gitkeep +0 -0
- package/stacks/nextjs-supabase/.morph/templates/infrastructure/github/workflows/cd-prod.yml.hbs +22 -0
- package/stacks/nextjs-supabase/.morph/templates/infrastructure/github/workflows/cd-staging.yml.hbs +22 -0
- package/stacks/nextjs-supabase/.morph/templates/infrastructure/github/workflows/ci-build.yml.hbs +35 -0
- package/stacks/nextjs-supabase/README.md +6 -15
- package/bin/render-template.js +0 -303
- package/bin/semantic-detect-agents.js +0 -247
- package/bin/validate-agents-skills.js +0 -257
- package/bin/validate-agents.js +0 -70
- package/bin/validate-phase.js +0 -263
- package/docs/examples.md +0 -328
- package/scripts/reorganize-skills.cjs +0 -175
- package/scripts/validate-agents-structure.cjs +0 -52
- package/scripts/validate-skills.cjs +0 -180
- package/src/commands/deploy.js +0 -780
- package/src/lib/continuous-validator.js +0 -421
- package/src/lib/decision-constraint-loader.js +0 -109
- package/src/lib/design-system-scaffolder.js +0 -299
- package/src/lib/hook-executor.js +0 -257
- package/src/lib/mockup-generator.js +0 -366
- package/src/lib/ui-detector.js +0 -350
- package/stacks/blazor-azure/.azure/README.md +0 -293
- package/stacks/blazor-azure/.azure/docs/azure-devops-setup.md +0 -454
- package/stacks/blazor-azure/.azure/docs/branch-strategy.md +0 -398
- package/stacks/blazor-azure/.azure/docs/local-development.md +0 -515
- package/stacks/blazor-azure/.azure/pipelines/pipeline-variables.yml +0 -34
- package/stacks/blazor-azure/.azure/pipelines/prod-pipeline.yml +0 -319
- package/stacks/blazor-azure/.azure/pipelines/staging-pipeline.yml +0 -234
- package/stacks/blazor-azure/.azure/pipelines/templates/build-dotnet.yml +0 -75
- package/stacks/blazor-azure/.azure/pipelines/templates/deploy-app-service.yml +0 -94
- package/stacks/blazor-azure/.azure/pipelines/templates/deploy-container-app.yml +0 -120
- package/stacks/blazor-azure/.azure/pipelines/templates/infra-deploy.yml +0 -90
- package/stacks/blazor-azure/.claude/settings.local.json +0 -15
- package/stacks/blazor-azure/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +0 -392
- package/stacks/blazor-azure/.morph/docs/workflows/design-impl.md +0 -37
- package/stacks/blazor-azure/.morph/docs/workflows/enforcement-pipeline.md +0 -668
- package/stacks/blazor-azure/.morph/docs/workflows/fast-track.md +0 -29
- package/stacks/blazor-azure/.morph/docs/workflows/full-morph.md +0 -76
- package/stacks/blazor-azure/.morph/docs/workflows/standard.md +0 -44
- package/stacks/blazor-azure/.morph/docs/workflows/ui-refresh.md +0 -39
- package/stacks/blazor-azure/.morph/examples/api-nextjs/README.md +0 -241
- package/stacks/blazor-azure/.morph/examples/api-nextjs/contracts.ts +0 -307
- package/stacks/blazor-azure/.morph/examples/api-nextjs/spec.md +0 -399
- package/stacks/blazor-azure/.morph/examples/api-nextjs/tasks.md +0 -168
- package/stacks/blazor-azure/.morph/examples/micro-saas/README.md +0 -125
- package/stacks/blazor-azure/.morph/examples/micro-saas/contracts.cs +0 -358
- package/stacks/blazor-azure/.morph/examples/micro-saas/decisions.md +0 -246
- package/stacks/blazor-azure/.morph/examples/micro-saas/spec.md +0 -236
- package/stacks/blazor-azure/.morph/examples/micro-saas/tasks.md +0 -150
- package/stacks/blazor-azure/.morph/examples/multi-agent/README.md +0 -309
- package/stacks/blazor-azure/.morph/examples/multi-agent/contracts.cs +0 -433
- package/stacks/blazor-azure/.morph/examples/multi-agent/spec.md +0 -479
- package/stacks/blazor-azure/.morph/examples/multi-agent/tasks.md +0 -185
- package/stacks/blazor-azure/.morph/examples/scheduled-reports/decisions.md +0 -158
- package/stacks/blazor-azure/.morph/examples/scheduled-reports/proposal.md +0 -95
- package/stacks/blazor-azure/.morph/examples/scheduled-reports/spec.md +0 -267
- package/stacks/blazor-azure/.morph/examples/state-v3.json +0 -188
- package/stacks/blazor-azure/.morph/hooks/README.md +0 -348
- package/stacks/blazor-azure/.morph/hooks/pre-commit-agents.sh +0 -24
- package/stacks/blazor-azure/.morph/hooks/pre-commit-all.sh +0 -48
- package/stacks/blazor-azure/.morph/hooks/pre-commit-specs.sh +0 -49
- package/stacks/blazor-azure/.morph/hooks/task-completed.js +0 -73
- package/stacks/blazor-azure/.morph/hooks/teammate-idle.js +0 -68
- package/stacks/blazor-azure/.morph/standards/agent-framework-blazor-ui.md +0 -359
- package/stacks/blazor-azure/.morph/standards/agent-framework-production.md +0 -410
- package/stacks/blazor-azure/.morph/standards/agent-framework-setup.md +0 -413
- package/stacks/blazor-azure/.morph/standards/agent-framework-workflows.md +0 -349
- package/stacks/blazor-azure/.morph/standards/agent-teams-workflow.md +0 -474
- package/stacks/blazor-azure/.morph/standards/architecture.md +0 -325
- package/stacks/blazor-azure/.morph/standards/azure.md +0 -605
- package/stacks/blazor-azure/.morph/standards/coding.md +0 -377
- package/stacks/blazor-azure/.morph/standards/dotnet10-migration.md +0 -520
- package/stacks/blazor-azure/.morph/standards/fluent-ui-setup.md +0 -590
- package/stacks/blazor-azure/.morph/standards/migration-guide.md +0 -514
- package/stacks/blazor-azure/.morph/standards/passkeys-auth.md +0 -423
- package/stacks/blazor-azure/.morph/standards/vector-search-rag.md +0 -536
- package/stacks/blazor-azure/.morph/templates/CONTEXT-FEATURE.md +0 -276
- package/stacks/blazor-azure/.morph/templates/CONTEXT.md +0 -170
- package/stacks/blazor-azure/.morph/templates/FluentDesignTheme.cs +0 -149
- package/stacks/blazor-azure/.morph/templates/MudTheme.cs +0 -281
- package/stacks/blazor-azure/.morph/templates/agent.cs +0 -163
- package/stacks/blazor-azure/.morph/templates/clarify-questions.md +0 -159
- package/stacks/blazor-azure/.morph/templates/component.razor +0 -239
- package/stacks/blazor-azure/.morph/templates/contracts/Commands.cs +0 -74
- package/stacks/blazor-azure/.morph/templates/contracts/Entities.cs +0 -25
- package/stacks/blazor-azure/.morph/templates/contracts/Queries.cs +0 -74
- package/stacks/blazor-azure/.morph/templates/contracts/README.md +0 -74
- package/stacks/blazor-azure/.morph/templates/contracts.cs +0 -217
- package/stacks/blazor-azure/.morph/templates/decisions.md +0 -123
- package/stacks/blazor-azure/.morph/templates/design-system.css +0 -226
- package/stacks/blazor-azure/.morph/templates/infra/.dockerignore.example +0 -89
- package/stacks/blazor-azure/.morph/templates/infra/Dockerfile.example +0 -82
- package/stacks/blazor-azure/.morph/templates/infra/README.md +0 -286
- package/stacks/blazor-azure/.morph/templates/infra/app-insights.bicep +0 -63
- package/stacks/blazor-azure/.morph/templates/infra/app-service.bicep +0 -164
- package/stacks/blazor-azure/.morph/templates/infra/azure-pipelines-deploy.yml +0 -480
- package/stacks/blazor-azure/.morph/templates/infra/container-app-env.bicep +0 -49
- package/stacks/blazor-azure/.morph/templates/infra/container-app.bicep +0 -156
- package/stacks/blazor-azure/.morph/templates/infra/deploy-checklist.md +0 -426
- package/stacks/blazor-azure/.morph/templates/infra/deploy.ps1 +0 -229
- package/stacks/blazor-azure/.morph/templates/infra/deploy.sh +0 -208
- package/stacks/blazor-azure/.morph/templates/infra/key-vault.bicep +0 -91
- package/stacks/blazor-azure/.morph/templates/infra/main.bicep +0 -189
- package/stacks/blazor-azure/.morph/templates/infra/parameters.dev.json +0 -29
- package/stacks/blazor-azure/.morph/templates/infra/parameters.prod.json +0 -29
- package/stacks/blazor-azure/.morph/templates/infra/parameters.staging.json +0 -29
- package/stacks/blazor-azure/.morph/templates/infra/sql-database.bicep +0 -103
- package/stacks/blazor-azure/.morph/templates/infra/storage.bicep +0 -106
- package/stacks/blazor-azure/.morph/templates/integrations/asaas-client.cs +0 -387
- package/stacks/blazor-azure/.morph/templates/integrations/asaas-webhook.cs +0 -351
- package/stacks/blazor-azure/.morph/templates/integrations/azure-identity-config.cs +0 -288
- package/stacks/blazor-azure/.morph/templates/integrations/clerk-config.cs +0 -258
- package/stacks/blazor-azure/.morph/templates/job.cs +0 -171
- package/stacks/blazor-azure/.morph/templates/migration.cs +0 -83
- package/stacks/blazor-azure/.morph/templates/proposal.md +0 -141
- package/stacks/blazor-azure/.morph/templates/recap.md +0 -94
- package/stacks/blazor-azure/.morph/templates/repository.cs +0 -141
- package/stacks/blazor-azure/.morph/templates/saas/subscription.cs +0 -347
- package/stacks/blazor-azure/.morph/templates/saas/tenant.cs +0 -338
- package/stacks/blazor-azure/.morph/templates/service.cs +0 -139
- package/stacks/blazor-azure/.morph/templates/simulation.md +0 -353
- package/stacks/blazor-azure/.morph/templates/spec.md +0 -149
- package/stacks/blazor-azure/.morph/templates/sprint-status.yaml +0 -68
- package/stacks/blazor-azure/.morph/templates/state.template.json +0 -222
- package/stacks/blazor-azure/.morph/templates/story.md +0 -143
- package/stacks/blazor-azure/.morph/templates/tasks.md +0 -257
- package/stacks/blazor-azure/.morph/templates/test.cs +0 -239
- package/stacks/blazor-azure/.morph/templates/ui-components.md +0 -362
- package/stacks/blazor-azure/.morph/templates/ui-design-system.md +0 -286
- package/stacks/blazor-azure/.morph/templates/ui-flows.md +0 -336
- package/stacks/blazor-azure/.morph/templates/ui-mockups.md +0 -133
- package/stacks/nextjs-supabase/.morph/docs/easypanel-setup.md +0 -169
- package/stacks/nextjs-supabase/.morph/docs/supabase-mcp-setup.md +0 -247
- package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/README.md +0 -697
- package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/spec.md +0 -85
- package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/tasks.md +0 -86
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/README.md +0 -498
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/decisions.md +0 -121
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/spec.md +0 -138
- package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/tasks.md +0 -162
- package/stacks/nextjs-supabase/.morph/standards/easypanel-deploy.md +0 -191
- package/stacks/nextjs-supabase/.morph/standards/nextjs-patterns.md +0 -193
- package/stacks/nextjs-supabase/.morph/standards/supabase-auth.md +0 -171
- package/stacks/nextjs-supabase/.morph/standards/supabase-pgvector.md +0 -164
- package/stacks/nextjs-supabase/.morph/standards/supabase-rls.md +0 -179
- package/stacks/nextjs-supabase/.morph/standards/supabase-storage.md +0 -148
- package/stacks/nextjs-supabase/.morph/templates/contracts.cs +0 -173
- package/stacks/nextjs-supabase/.morph/templates/contracts.ts +0 -168
- package/stacks/nextjs-supabase/.morph/templates/decisions.md +0 -115
- package/stacks/nextjs-supabase/.morph/templates/dockerfile-api.dockerfile +0 -38
- package/stacks/nextjs-supabase/.morph/templates/dockerfile-web.dockerfile +0 -48
- package/stacks/nextjs-supabase/.morph/templates/proposal.md +0 -145
- package/stacks/nextjs-supabase/.morph/templates/recap.md +0 -134
- package/stacks/nextjs-supabase/.morph/templates/rls-policy.sql +0 -57
- package/stacks/nextjs-supabase/.morph/templates/spec.md +0 -231
- package/stacks/nextjs-supabase/.morph/templates/supabase-migration.sql +0 -100
- package/stacks/nextjs-supabase/.morph/templates/tasks.md +0 -257
- /package/src/lib/{design-system-generator.js → generators/design-system-generator.js} +0 -0
- /package/src/lib/{learning-system.js → learning/learning-system.js} +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,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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Sanitization patterns - Whitelists, Blacklists, Secret patterns
|
|
3
|
+
* @module morph-spec/sanitizer/patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Whitelist: Files explicitly allowed to be read and sent to LLM
|
|
8
|
+
*/
|
|
9
|
+
export const WHITELIST_FILES = [
|
|
10
|
+
'package.json',
|
|
11
|
+
'package-lock.json',
|
|
12
|
+
'yarn.lock',
|
|
13
|
+
'pnpm-lock.yaml',
|
|
14
|
+
'*.csproj',
|
|
15
|
+
'*.sln',
|
|
16
|
+
'README.md',
|
|
17
|
+
'CLAUDE.md',
|
|
18
|
+
'Dockerfile',
|
|
19
|
+
'docker-compose.yml',
|
|
20
|
+
'docker-compose.*.yml',
|
|
21
|
+
'*.bicep',
|
|
22
|
+
'azure-pipelines.yml',
|
|
23
|
+
'.github/workflows/*.yml',
|
|
24
|
+
'.gitlab-ci.yml',
|
|
25
|
+
'tsconfig.json',
|
|
26
|
+
'jsconfig.json',
|
|
27
|
+
'.eslintrc*',
|
|
28
|
+
'.prettierrc*',
|
|
29
|
+
'vite.config.*',
|
|
30
|
+
'next.config.*',
|
|
31
|
+
'nuxt.config.*'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Blacklist: Directories to NEVER scan or read
|
|
36
|
+
*/
|
|
37
|
+
export const BLACKLIST_DIRECTORIES = [
|
|
38
|
+
'node_modules',
|
|
39
|
+
'.git',
|
|
40
|
+
'.svn',
|
|
41
|
+
'.hg',
|
|
42
|
+
'bin',
|
|
43
|
+
'obj',
|
|
44
|
+
'dist',
|
|
45
|
+
'build',
|
|
46
|
+
'out',
|
|
47
|
+
'.next',
|
|
48
|
+
'.nuxt',
|
|
49
|
+
'coverage',
|
|
50
|
+
'__pycache__',
|
|
51
|
+
'.pytest_cache',
|
|
52
|
+
'venv',
|
|
53
|
+
'env',
|
|
54
|
+
'.venv',
|
|
55
|
+
'.env.*',
|
|
56
|
+
'temp',
|
|
57
|
+
'tmp',
|
|
58
|
+
'.cache'
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Blacklist: File patterns to NEVER read (even if matched by whitelist)
|
|
63
|
+
*/
|
|
64
|
+
export const BLACKLIST_FILES = [
|
|
65
|
+
'.env',
|
|
66
|
+
'.env.local',
|
|
67
|
+
'.env.*.local',
|
|
68
|
+
'.env.production',
|
|
69
|
+
'*.key',
|
|
70
|
+
'*.pem',
|
|
71
|
+
'*.pfx',
|
|
72
|
+
'*.p12',
|
|
73
|
+
'*.cert',
|
|
74
|
+
'*.crt',
|
|
75
|
+
'*secret*',
|
|
76
|
+
'*password*',
|
|
77
|
+
'*credentials*',
|
|
78
|
+
'*token*',
|
|
79
|
+
'secrets.json',
|
|
80
|
+
'appsettings.Production.json',
|
|
81
|
+
'appsettings.Staging.json',
|
|
82
|
+
'*.log',
|
|
83
|
+
'*.sqlite',
|
|
84
|
+
'*.db'
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Secret patterns to redact from file contents
|
|
89
|
+
* Each pattern has: regex, replacement text, description
|
|
90
|
+
*/
|
|
91
|
+
export const SECRET_PATTERNS = [
|
|
92
|
+
{
|
|
93
|
+
name: 'api-key',
|
|
94
|
+
regex: /(?:api[_-]?key|apikey|api[_-]?secret)[\s:=]+["']?([a-zA-Z0-9_\-]{20,})["']?/gi,
|
|
95
|
+
replacement: 'API_KEY=***REDACTED***',
|
|
96
|
+
description: 'API keys'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'bearer-token',
|
|
100
|
+
regex: /(?:bearer|authorization)[\s:=]+["']?([a-zA-Z0-9_\-\.]{20,})["']?/gi,
|
|
101
|
+
replacement: 'Bearer ***REDACTED***',
|
|
102
|
+
description: 'Bearer tokens'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'connection-string',
|
|
106
|
+
regex: /(?:connection[_-]?string|connectionstring)[\s:=]+["']?([^"'\n]{30,})["']?/gi,
|
|
107
|
+
replacement: 'ConnectionString=***REDACTED***',
|
|
108
|
+
description: 'Connection strings'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'sql-password',
|
|
112
|
+
regex: /(?:password|pwd)=([^;"\s]{6,})/gi,
|
|
113
|
+
replacement: 'Password=***REDACTED***',
|
|
114
|
+
description: 'SQL passwords in connection strings'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'azure-key',
|
|
118
|
+
regex: /(?:account[_-]?key|accountkey)[\s:=]+["']?([a-zA-Z0-9+/=]{40,})["']?/gi,
|
|
119
|
+
replacement: 'AccountKey=***REDACTED***',
|
|
120
|
+
description: 'Azure Storage Account Keys'
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'jwt-secret',
|
|
124
|
+
regex: /(?:jwt[_-]?secret|jwtsecret)[\s:=]+["']?([a-zA-Z0-9_\-]{16,})["']?/gi,
|
|
125
|
+
replacement: 'JwtSecret=***REDACTED***',
|
|
126
|
+
description: 'JWT secrets'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'private-key',
|
|
130
|
+
regex: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----[\s\S]+?-----END (?:RSA |EC )?PRIVATE KEY-----/gi,
|
|
131
|
+
replacement: '-----BEGIN PRIVATE KEY-----\n***REDACTED***\n-----END PRIVATE KEY-----',
|
|
132
|
+
description: 'Private keys (PEM format)'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'github-token',
|
|
136
|
+
regex: /gh[pousr]_[a-zA-Z0-9]{36,}/gi,
|
|
137
|
+
replacement: 'ghp_***REDACTED***',
|
|
138
|
+
description: 'GitHub personal access tokens'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'npm-token',
|
|
142
|
+
regex: /npm_[a-zA-Z0-9]{36,}/gi,
|
|
143
|
+
replacement: 'npm_***REDACTED***',
|
|
144
|
+
description: 'NPM tokens'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'generic-secret',
|
|
148
|
+
regex: /(?:secret|password|pwd|pass|token)[\s:=]+["']([^"'\n]{8,})["']/gi,
|
|
149
|
+
replacement: 'secret=***REDACTED***',
|
|
150
|
+
description: 'Generic secrets'
|
|
151
|
+
}
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Maximum file size to include (50KB)
|
|
156
|
+
* Files larger than this are truncated with a summary
|
|
157
|
+
*/
|
|
158
|
+
export const MAX_FILE_SIZE = 50 * 1024; // 50KB
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Maximum content length for README/CLAUDE.md summaries
|
|
162
|
+
*/
|
|
163
|
+
export const MAX_SUMMARY_LENGTH = 500; // 500 chars
|