@polymorphism-tech/morph-spec 4.3.6 → 4.5.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/.morph/.morphversion +3 -3
- package/.morph/analytics/threads-log.jsonl +44 -9
- package/.morph/config/config.json +2 -3
- package/.morph/framework/standards/STANDARDS.json +812 -0
- package/.morph/{standards → framework/standards}/ai-agents/team-orchestration.md +3 -3
- package/.morph/framework/standards/integration/mcp/mcp-tools.md +384 -0
- package/.morph/{templates → framework/templates}/README.md +17 -17
- package/.morph/{templates → framework/templates}/REGISTRY.json +48 -233
- package/.morph/framework/templates/code/dotnet/contracts/contracts.cs.hbs +172 -0
- package/.morph/{templates → framework/templates}/context/CONTEXT-FEATURE.md +1 -1
- package/.morph/{templates → framework/templates}/context/CONTEXT.md +3 -3
- package/.morph/framework/templates/docs/clarifications.md +253 -0
- package/.morph/framework/templates/docs/onboarding.md +123 -0
- package/.morph/framework/templates/docs/schema-analysis.md +119 -0
- package/.morph/{templates → framework/templates}/docs/spec.md +149 -149
- package/.morph/framework/templates/docs/ui-components.md +124 -0
- package/.morph/framework/templates/docs/ui-design-system.md +76 -0
- package/.morph/framework/templates/docs/ui-flows.md +167 -0
- package/.morph/framework/templates/docs/ui-mockups.md +98 -0
- package/.morph/{templates → framework/templates}/examples/spec-examples.md +1 -1
- package/.morph/{templates → framework/templates}/infrastructure/github/README.md +11 -11
- package/.morph/{templates → framework/templates}/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +2 -2
- package/.morph/{templates → framework/templates}/meta-prompts/parallel-workers/parallel-worker.md +2 -2
- package/.morph/{templates → framework/templates}/meta-prompts/validators/pre-commit-validator.md +1 -1
- package/.morph/logs/tool-failures.log +51 -0
- package/.morph/memory/pre-compact-2026-02-22T17-01-01-658Z.json +16 -0
- package/.morph/state.json +1 -1
- package/CLAUDE.md +20 -119
- package/README.md +20 -18
- package/bin/detect-agents.js +1 -1
- package/bin/morph-spec.js +116 -266
- package/bin/task-manager.cjs +2 -2
- package/bin/validate.js +1 -1
- package/claude-plugin.json +14 -0
- package/docs/claude-alignment-report.md +137 -0
- package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +512 -0
- package/docs/plans/2026-02-22-claude-settings.md +515 -0
- package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +728 -0
- package/docs/plans/2026-02-22-morph-spec-next.md +478 -0
- package/docs/plans/2026-02-22-native-alignment-design.md +199 -0
- package/docs/plans/2026-02-22-native-alignment-impl.md +925 -0
- package/docs/plans/2026-02-22-native-enrichment-design.md +244 -0
- package/docs/plans/2026-02-22-native-enrichment.md +735 -0
- package/framework/CLAUDE.md +77 -0
- package/framework/commands/morph-apply.md +9 -9
- package/framework/commands/morph-archive.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-proposal.md +9 -9
- package/framework/commands/morph-status.md +3 -3
- package/framework/commands/morph-troubleshoot.md +1 -1
- package/framework/hooks/README.md +201 -282
- package/framework/hooks/claude-code/notification/approval-reminder.js +52 -0
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +83 -0
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +42 -0
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +61 -0
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +71 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +58 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +64 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +94 -0
- package/framework/hooks/claude-code/statusline.py +239 -0
- package/framework/hooks/claude-code/statusline.sh +7 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +88 -0
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +91 -0
- package/framework/hooks/shared/hook-response.js +45 -0
- package/framework/hooks/shared/phase-utils.js +129 -0
- package/framework/hooks/shared/state-reader.js +138 -0
- package/framework/hooks/shared/stdin-reader.js +26 -0
- package/framework/phases.json +145 -0
- package/framework/rules/csharp-standards.md +10 -0
- package/framework/rules/frontend-standards.md +14 -0
- package/framework/rules/infrastructure-standards.md +13 -0
- package/framework/rules/morph-workflow.md +86 -0
- package/framework/rules/testing-standards.md +11 -0
- package/framework/skills/level-0-meta/brainstorming.md +133 -0
- package/framework/skills/level-0-meta/code-review.md +12 -4
- package/framework/skills/level-0-meta/mcp-registry.json +207 -0
- package/framework/skills/level-0-meta/morph-checklist.md +9 -1
- package/framework/skills/level-0-meta/simulation-checklist.md +9 -1
- package/framework/skills/level-0-meta/tool-usage-guide.md +335 -0
- package/framework/skills/level-0-meta/verification-before-completion.md +145 -0
- package/framework/skills/level-1-workflows/morph-replicate.md +9 -1
- package/framework/skills/level-1-workflows/phase-clarify.md +65 -4
- package/framework/skills/level-1-workflows/phase-codebase-analysis.md +182 -0
- package/framework/skills/level-1-workflows/phase-design.md +342 -80
- package/framework/skills/level-1-workflows/phase-implement.md +254 -0
- package/framework/skills/level-1-workflows/phase-setup.md +76 -10
- package/framework/skills/level-1-workflows/phase-tasks.md +88 -7
- package/framework/skills/level-1-workflows/phase-uiux.md +95 -17
- package/framework/skills/level-2-domains/ai-agents/ai-system-architect.md +8 -1
- package/framework/skills/level-2-domains/architecture/po-pm-advisor.md +8 -1
- package/framework/skills/level-2-domains/architecture/prompt-engineer.md +8 -1
- package/framework/skills/level-2-domains/architecture/seo-growth-hacker.md +8 -1
- package/framework/skills/level-2-domains/architecture/standards-architect.md +11 -4
- package/framework/skills/level-2-domains/backend/api-designer.md +8 -1
- package/framework/skills/level-2-domains/backend/dotnet-senior.md +8 -1
- package/framework/skills/level-2-domains/backend/ef-modeler.md +8 -1
- package/framework/skills/level-2-domains/backend/hangfire-orchestrator.md +9 -2
- package/framework/skills/level-2-domains/backend/ms-agent-expert.md +8 -1
- package/framework/skills/level-2-domains/frontend/blazor-builder.md +8 -1
- package/framework/skills/level-2-domains/frontend/nextjs-expert.md +8 -1
- package/framework/skills/level-2-domains/frontend/ui-ux-designer.md +9 -2
- package/framework/skills/level-2-domains/infrastructure/azure-architect.md +8 -1
- package/framework/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +8 -1
- package/framework/skills/level-2-domains/infrastructure/bicep-architect.md +8 -1
- package/framework/skills/level-2-domains/infrastructure/container-specialist.md +8 -1
- package/framework/skills/level-2-domains/infrastructure/devops-engineer.md +8 -1
- package/framework/skills/level-2-domains/integrations/asaas-financial.md +8 -1
- package/framework/skills/level-2-domains/integrations/azure-identity.md +8 -1
- package/framework/skills/level-2-domains/integrations/clerk-auth.md +8 -1
- package/framework/skills/level-2-domains/integrations/{hangfire-orchestrator.md → hangfire-integration.md} +8 -1
- package/framework/skills/level-2-domains/integrations/resend-email.md +8 -1
- package/framework/skills/level-2-domains/quality/code-analyzer.md +10 -3
- package/framework/skills/level-2-domains/quality/testing-specialist.md +8 -1
- package/framework/standards/STANDARDS.json +812 -0
- package/framework/standards/ai-agents/team-orchestration.md +3 -3
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +17 -0
- package/framework/standards/integration/mcp/mcp-tools.md +384 -0
- package/framework/templates/README.md +17 -17
- package/framework/templates/REGISTRY.json +48 -233
- package/framework/templates/code/dotnet/contracts/contracts.cs.hbs +172 -0
- package/framework/templates/context/CONTEXT-FEATURE.md +1 -1
- package/framework/templates/context/CONTEXT.md +3 -3
- package/framework/templates/docs/clarifications.md +253 -0
- package/framework/templates/docs/onboarding.md +123 -0
- package/framework/templates/docs/schema-analysis.md +119 -0
- package/framework/templates/docs/spec.md +149 -149
- package/framework/templates/docs/ui-components.md +124 -0
- package/framework/templates/docs/ui-design-system.md +76 -0
- package/framework/templates/docs/ui-flows.md +167 -0
- package/framework/templates/docs/ui-mockups.md +98 -0
- package/framework/templates/docs/user-stories.md +34 -0
- package/framework/templates/examples/spec-examples.md +1 -1
- package/framework/templates/infrastructure/github/README.md +11 -11
- package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +2 -2
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +1 -1
- package/framework/workflows/configs/express.json +45 -0
- package/framework/workflows/configs/spec-only.json +43 -0
- package/framework/workflows/docs/enforcement-pipeline.md +8 -8
- package/framework/workflows/docs/full-morph.md +3 -3
- package/package.json +3 -2
- package/scripts/generate-refs.js +336 -0
- package/scripts/generate-standards-registry.js +44 -0
- package/scripts/validate-real.mjs +255 -0
- package/src/commands/feature/create-story.js +362 -361
- package/src/commands/feature/shard-spec.js +225 -224
- package/src/commands/feature/sprint-status.js +1 -1
- package/src/commands/generation/generate-onboarding.js +169 -0
- package/src/commands/generation/generate.js +2 -2
- package/src/commands/mcp/mcp-setup.js +315 -0
- package/src/commands/project/changes.js +66 -0
- package/src/commands/project/checkpoint.js +209 -0
- package/src/commands/project/cost.js +179 -0
- package/src/commands/project/diff.js +278 -0
- package/src/commands/project/doctor.js +55 -7
- package/src/commands/project/init.js +318 -76
- package/src/commands/project/revert.js +173 -0
- package/src/commands/project/standards.js +80 -0
- package/src/commands/project/status.js +376 -0
- package/src/commands/project/update-agents.js +23 -0
- package/src/commands/project/update.js +63 -30
- package/src/commands/state/advance-phase.js +4 -3
- package/src/commands/state/state.js +10 -3
- package/src/commands/state/validate-phase.js +19 -2
- package/src/commands/templates/template-customize.js +4 -4
- package/src/commands/templates/template-render.js +1 -1
- package/src/commands/templates/template-show.js +1 -1
- package/src/commands/validation/validate-feature.js +359 -0
- package/src/core/orchestrator.js +3 -38
- package/src/core/paths/output-schema.js +135 -0
- package/src/core/state/state-manager.js +831 -592
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/workflows/workflow-detector.js +17 -1
- package/src/lib/agents/micro-agent-factory.js +1 -1
- package/src/lib/context/context-bundler.js +2 -1
- package/src/lib/detectors/claude-config-detector.js +392 -0
- package/src/lib/detectors/conversation-analyzer.js +4 -4
- package/src/lib/detectors/design-system-detector.js +6 -5
- package/src/lib/detectors/standards-generator.js +2 -2
- package/src/lib/generators/context-generator.js +539 -538
- package/src/lib/generators/recap-generator.js +1 -1
- package/src/lib/generators/settings-generator.js +210 -0
- package/src/lib/hooks/hook-executor.js +1 -1
- package/src/lib/installers/mcp-installer.js +299 -0
- package/src/lib/learning/learning-system.js +3 -3
- package/src/lib/orchestration/team-orchestrator.js +1 -1
- package/src/lib/standards/standards-context-injector.js +7 -7
- package/src/lib/threads/thread-coordinator.js +1 -1
- package/src/lib/troubleshooting/troubleshoot-grep.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -273
- package/src/lib/validators/design-system/design-system-validator.js +1 -1
- package/src/lib/validators/spec-validator.js +258 -258
- package/src/lib/validators/validation-runner.js +270 -269
- package/src/utils/agents-installer.js +206 -0
- package/src/utils/claude-settings-manager.js +258 -0
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +354 -28
- package/src/utils/skills-installer.js +74 -0
- package/.morph/project/context/detection-log.md +0 -16
- package/.morph/project/standards/inferred.md +0 -59
- package/framework/hooks/agent-stop/validate-and-continue.js +0 -96
- package/framework/hooks/agent-stop/validate-checkpoints.js +0 -101
- package/framework/hooks/agent-stop/validate-tests.js +0 -109
- package/framework/hooks/agent-teams/dispatch.js +0 -67
- package/framework/hooks/agent-teams/phase-advanced.js +0 -80
- package/framework/hooks/agent-teams/task-completed.js +0 -76
- package/framework/hooks/agent-teams/teammate-idle.js +0 -70
- package/src/commands/agents/agents-fuse.js +0 -97
- package/src/commands/agents/micro-agent.js +0 -112
- package/src/commands/agents/spawn-team.js +0 -237
- package/src/commands/agents/squad-template.js +0 -146
- package/src/commands/analytics/analytics.js +0 -176
- package/src/commands/context/context-prime.js +0 -63
- package/src/commands/context/core-four.js +0 -54
- package/src/commands/generation/generate-context.js +0 -40
- package/src/commands/project/detect-agents.js +0 -207
- package/src/commands/project/detect-workflow.js +0 -174
- package/src/commands/threads/thread-template.js +0 -103
- package/src/commands/threads/threads.js +0 -261
- package/src/commands/utils/session-summary.js +0 -291
- package/src/llm/analyzer.js +0 -215
- package/src/llm/few-shot-examples.js +0 -216
- package/src/llm/project-config-schema.json +0 -188
- package/src/llm/prompt-builder.js +0 -96
- /package/.morph/{project/context → context}/README.md +0 -0
- /package/.morph/{config → framework}/agents.json +0 -0
- /package/.morph/{standards → framework/standards}/ai-agents/blazor-ui.md +0 -0
- /package/.morph/{standards → framework/standards}/ai-agents/production.md +0 -0
- /package/.morph/{standards → framework/standards}/ai-agents/setup.md +0 -0
- /package/.morph/{standards → framework/standards}/ai-agents/workflows.md +0 -0
- /package/.morph/{standards → framework/standards}/architecture/ddd/aggregates.md +0 -0
- /package/.morph/{standards → framework/standards}/architecture/ddd/entities.md +0 -0
- /package/.morph/{standards → framework/standards}/architecture/ddd/value-objects.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/api/minimal-api.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/api/rest.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/api/validation.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/authentication/passkeys.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/database/ef-core.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/database/migrations.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/database/postgresql/database.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/database/repository-patterns.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/database/vector-search-rag.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/dotnet/async.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/dotnet/core.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/dotnet/di.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/dotnet/program-cs-checklist.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/integrations/asaas/asaas-api.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/integrations/clerk/clerk-auth.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/integrations/hangfire/hangfire-jobs.md +0 -0
- /package/.morph/{standards → framework/standards}/backend/integrations/resend/resend-email.md +0 -0
- /package/.morph/{standards → framework/standards}/context/analytics.md +0 -0
- /package/.morph/{standards → framework/standards}/context/bundles.md +0 -0
- /package/.morph/{standards → framework/standards}/context/priming.md +0 -0
- /package/.morph/{standards → framework/standards}/core/architecture.md +0 -0
- /package/.morph/{standards → framework/standards}/core/coding.md +0 -0
- /package/.morph/{standards → framework/standards}/core/git-branching-strategy.md +0 -0
- /package/.morph/{standards → framework/standards}/core/git.md +0 -0
- /package/.morph/{standards → framework/standards}/core/testing.md +0 -0
- /package/.morph/{standards → framework/standards}/data/nosql/blob-storage.md +0 -0
- /package/.morph/{standards → framework/standards}/data/nosql/cache/redis.md +0 -0
- /package/.morph/{standards → framework/standards}/data/nosql/cosmos-db.md +0 -0
- /package/.morph/{standards → framework/standards}/data/vector-search/azure-ai-search.md +0 -0
- /package/.morph/{standards → framework/standards}/data/vector-search/rag-chunking.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/blazor/design-checklist.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/blazor/fluent-ui-setup.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/blazor/fluent-ui.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/blazor/html-conversion.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/blazor/lifecycle.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/blazor/pitfalls.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/blazor/state.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/design-system/animations.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/design-system/naming.md +0 -0
- /package/.morph/{standards → framework/standards}/frontend/nextjs/nextjs-patterns.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/azure/azure.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/azure/bicep/bicep-patterns.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/azure/devops/azure-devops-setup.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/azure/devops/local-development.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/azure/services/functions.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/azure/services/service-bus.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/azure/services/storage.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/docker/easypanel-deploy.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/supabase/mcp-setup.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/supabase/supabase-auth.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/supabase/supabase-pgvector.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/supabase/supabase-rls.md +0 -0
- /package/.morph/{standards → framework/standards}/infrastructure/supabase/supabase-storage.md +0 -0
- /package/.morph/{standards → framework/standards}/integration/api/graphql.md +0 -0
- /package/.morph/{standards → framework/standards}/integration/api/grpc.md +0 -0
- /package/.morph/{standards → framework/standards}/integration/api/rest-design.md +0 -0
- /package/.morph/{standards → framework/standards}/integration/event-driven/cqrs.md +0 -0
- /package/.morph/{standards → framework/standards}/integration/event-driven/event-sourcing.md +0 -0
- /package/.morph/{standards → framework/standards}/integration/event-driven/service-bus.md +0 -0
- /package/.morph/{standards → framework/standards}/observability/logging.md +0 -0
- /package/.morph/{standards → framework/standards}/observability/metrics.md +0 -0
- /package/.morph/{standards → framework/standards}/observability/monitoring.md +0 -0
- /package/.morph/{standards → framework/standards}/observability/tracing.md +0 -0
- /package/.morph/{standards → framework/standards}/workflows/parallel-execution.md +0 -0
- /package/.morph/{standards → framework/standards}/workflows/thread-management.md +0 -0
- /package/.morph/{templates → framework/templates}/.idea/morph-templates.xml +0 -0
- /package/.morph/{templates → framework/templates}/.vscode/morph-templates.code-snippets +0 -0
- /package/.morph/{templates → framework/templates}/IDE-SNIPPETS.md +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/backend/repository.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/backend/service.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/contracts/Commands.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/contracts/Entities.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/contracts/Queries.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/contracts/README.md +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/contracts/api-contracts.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/contracts/contracts.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/database/migration.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/frontend/component.razor +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/jobs/agent.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/jobs/job.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/dotnet/test.cs +0 -0
- /package/.morph/{templates → framework/templates}/code/sql/rls-policy.sql +0 -0
- /package/.morph/{templates → framework/templates}/code/sql/supabase-migration.sql +0 -0
- /package/.morph/{templates → framework/templates}/code/sql/supabase-migration.template.sql +0 -0
- /package/.morph/{templates → framework/templates}/code/typescript/contracts.ts +0 -0
- /package/.morph/{templates → framework/templates}/docs/proposal.md +0 -0
- /package/.morph/{templates → framework/templates}/examples/design-system-examples.md +0 -0
- /package/.morph/{templates → framework/templates}/feature/decisions.md +0 -0
- /package/.morph/{templates → framework/templates}/feature/recap.md +0 -0
- /package/.morph/{templates → framework/templates}/feature/tasks.md +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/Dockerfile.example +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/README.md +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/app-insights.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/app-service.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/container-app-env.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/container-app.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/deploy-checklist.md +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/deploy.ps1 +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/deploy.sh +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/key-vault.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/main.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/parameters.dev.json +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/parameters.prod.json +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/parameters.staging.json +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/sql-database.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/azure/storage.bicep +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/docker/Dockerfile.template +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/docker/docker-compose.template.yml +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/docker/dockerfile-api.dockerfile +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/docker/dockerfile-web.dockerfile +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/docker/easypanel.template.json +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/github/actions/docker-build-push/action.yml.hbs +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/github/actions/health-check/action.yml.hbs +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/github/workflows/docker-build-push.yml.hbs +0 -0
- /package/.morph/{templates → framework/templates}/infrastructure/github/workflows/dotnet-build.yml.hbs +0 -0
- /package/.morph/{templates → framework/templates}/integrations/asaas-client.cs +0 -0
- /package/.morph/{templates → framework/templates}/integrations/asaas-webhook.cs +0 -0
- /package/.morph/{templates → framework/templates}/integrations/azure-identity-config.cs +0 -0
- /package/.morph/{templates → framework/templates}/integrations/clerk-config.cs +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/fusion/fusion-agent.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/fusion/fusion-aggregator.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/hops/hop-retry.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/hops/hop-validation.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/hops/hop-wrapper.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/parallel-workers/parallel-coordinator.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/squad-leaders/backend-squad.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/squad-leaders/frontend-squad.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/squad-leaders/squad-leader.md +0 -0
- /package/.morph/{templates → framework/templates}/meta-prompts/validators/checkpoint-validator.md +0 -0
- /package/.morph/{templates → framework/templates}/saas/subscription.cs +0 -0
- /package/.morph/{templates → framework/templates}/saas/tenant.cs +0 -0
- /package/.morph/{templates → framework/templates}/state.template.json +0 -0
- /package/.morph/{templates → framework/templates}/ui/FluentDesignTheme.cs +0 -0
- /package/.morph/{templates → framework/templates}/ui/MudTheme.cs +0 -0
- /package/.morph/{templates → framework/templates}/ui/design-system.css +0 -0
- /package/framework/hooks/{commit-msg → git/commit-msg}/conventional-commits.sh +0 -0
- /package/framework/hooks/{pre-commit → git/pre-commit}/agents.sh +0 -0
- /package/framework/hooks/{pre-commit → git/pre-commit}/orchestrator.sh +0 -0
- /package/framework/hooks/{pre-commit → git/pre-commit}/specs.sh +0 -0
- /package/framework/hooks/{pre-push → git/pre-push}/run-tests.sh +0 -0
|
@@ -1,592 +1,831 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC State Manager Library
|
|
3
|
-
*
|
|
4
|
-
* Manages state.json for tracking features, progress, agents, and checkpoints.
|
|
5
|
-
* Used both by CLI commands and internal automation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
-
import { join, dirname } from 'path';
|
|
10
|
-
import { detectWorkflow } from '../workflows/workflow-detector.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
state.features[featureName]
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
*
|
|
287
|
-
* @param {string}
|
|
288
|
-
* @param {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
*
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
state
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC State Manager Library
|
|
3
|
+
*
|
|
4
|
+
* Manages state.json for tracking features, progress, agents, and checkpoints.
|
|
5
|
+
* Used both by CLI commands and internal automation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { detectWorkflow } from '../workflows/workflow-detector.js';
|
|
11
|
+
import { getAllOutputPaths, getOutputPath } from '../paths/output-schema.js';
|
|
12
|
+
|
|
13
|
+
const STATE_FILE_NAME = '.morph/state.json';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Core Functions
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the state file path (looks in current working directory)
|
|
21
|
+
*/
|
|
22
|
+
export function getStatePath() {
|
|
23
|
+
return join(process.cwd(), STATE_FILE_NAME);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Derive current phase from filesystem — checks for phase folders in descending order.
|
|
27
|
+
* Returns the phase corresponding to the highest-numbered folder present.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} featurePath - Absolute path to .morph/features/{feature}/
|
|
30
|
+
* @returns {string} Phase name: 'implement' | 'tasks' | 'uiux' | 'design' | 'proposal' | 'setup'
|
|
31
|
+
*/
|
|
32
|
+
export function derivePhase(featurePath) {
|
|
33
|
+
const phaseMap = [
|
|
34
|
+
['4-implement', 'implement'],
|
|
35
|
+
['3-tasks', 'tasks'],
|
|
36
|
+
['2-ui', 'uiux'],
|
|
37
|
+
['1-design', 'design'],
|
|
38
|
+
['0-proposal', 'proposal'],
|
|
39
|
+
];
|
|
40
|
+
for (const [folder, phase] of phaseMap) {
|
|
41
|
+
if (existsSync(join(featurePath, folder))) return phase;
|
|
42
|
+
}
|
|
43
|
+
return 'setup';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Derive output existence from filesystem — checks if each output file exists at its expected path.
|
|
48
|
+
* Returns an object matching the old outputs shape for backwards-compatible display.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} featureName - Feature name
|
|
51
|
+
* @param {string} [baseDir] - Project base dir (defaults to cwd)
|
|
52
|
+
* @returns {Object} Map of outputType -> { created: boolean, path: string }
|
|
53
|
+
*/
|
|
54
|
+
export function deriveOutputs(featureName, baseDir = process.cwd()) {
|
|
55
|
+
const outputPaths = getAllOutputPaths(featureName);
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const [type, { path: relPath }] of Object.entries(outputPaths)) {
|
|
58
|
+
const absPath = join(baseDir, relPath);
|
|
59
|
+
result[type] = { created: existsSync(absPath), path: relPath };
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if state file exists
|
|
66
|
+
*/
|
|
67
|
+
export function stateExists() {
|
|
68
|
+
return existsSync(getStatePath());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Load state from disk
|
|
73
|
+
* @param {boolean} throwOnError - If false, returns null instead of throwing
|
|
74
|
+
* @returns {Object|null} State object or null
|
|
75
|
+
*/
|
|
76
|
+
export function loadState(throwOnError = true) {
|
|
77
|
+
const statePath = getStatePath();
|
|
78
|
+
|
|
79
|
+
if (!existsSync(statePath)) {
|
|
80
|
+
if (throwOnError) {
|
|
81
|
+
throw new Error(`State file not found: ${statePath}\nRun 'morph-spec state init' first.`);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const content = readFileSync(statePath, 'utf8');
|
|
88
|
+
const state = JSON.parse(content);
|
|
89
|
+
|
|
90
|
+
// Migrate v3.0.0 → v4.0.0 (new directory layout)
|
|
91
|
+
if (state.version === '3.0.0') {
|
|
92
|
+
state.version = '4.0.0';
|
|
93
|
+
for (const [name, feature] of Object.entries(state.features || {})) {
|
|
94
|
+
if (!feature.outputs) continue;
|
|
95
|
+
const outputPathMap = getAllOutputPaths(name);
|
|
96
|
+
for (const [key, { path: newPath }] of Object.entries(outputPathMap)) {
|
|
97
|
+
if (feature.outputs[key]) {
|
|
98
|
+
feature.outputs[key].path = newPath;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Migrate v4.x -> v5.0.0: remove outputs and phase (now derived from filesystem)
|
|
106
|
+
if (state.version && state.version.startsWith('4.')) {
|
|
107
|
+
state.version = '5.0.0';
|
|
108
|
+
for (const feature of Object.values(state.features || {})) {
|
|
109
|
+
delete feature.outputs;
|
|
110
|
+
delete feature.phase;
|
|
111
|
+
}
|
|
112
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return state;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (throwOnError) {
|
|
118
|
+
throw new Error(`Failed to parse state.json: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Save state to disk
|
|
126
|
+
* @param {Object} state - State object to save
|
|
127
|
+
*/
|
|
128
|
+
export function saveState(state) {
|
|
129
|
+
state.metadata = state.metadata || {};
|
|
130
|
+
state.metadata.lastUpdated = new Date().toISOString();
|
|
131
|
+
|
|
132
|
+
const statePath = getStatePath();
|
|
133
|
+
const stateDir = dirname(statePath);
|
|
134
|
+
|
|
135
|
+
// Ensure .morph directory exists
|
|
136
|
+
if (!existsSync(stateDir)) {
|
|
137
|
+
mkdirSync(stateDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Initialize new state file
|
|
145
|
+
* @param {Object} options - Options
|
|
146
|
+
* @param {boolean} options.force - Overwrite existing file
|
|
147
|
+
* @param {string} options.projectName - Project name
|
|
148
|
+
* @param {string} options.projectType - Project type (e.g., 'blazor-server')
|
|
149
|
+
* @returns {Object} Initial state
|
|
150
|
+
*/
|
|
151
|
+
export function initState(options = {}) {
|
|
152
|
+
const { force = false, projectName = '{PROJECT_NAME}', projectType = 'blazor-server' } = options;
|
|
153
|
+
|
|
154
|
+
if (stateExists() && !force) {
|
|
155
|
+
throw new Error('State file already exists. Use force=true to overwrite.');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const initialState = {
|
|
159
|
+
version: "5.0.0",
|
|
160
|
+
project: {
|
|
161
|
+
name: projectName,
|
|
162
|
+
type: projectType,
|
|
163
|
+
createdAt: new Date().toISOString(),
|
|
164
|
+
updatedAt: new Date().toISOString()
|
|
165
|
+
},
|
|
166
|
+
features: {},
|
|
167
|
+
threads: {},
|
|
168
|
+
metadata: {
|
|
169
|
+
totalFeatures: 0,
|
|
170
|
+
completedFeatures: 0,
|
|
171
|
+
totalTimeSpent: 0,
|
|
172
|
+
lastUpdated: new Date().toISOString()
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
saveState(initialState);
|
|
177
|
+
return initialState;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// Feature Operations
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get feature from state
|
|
186
|
+
* @param {string} featureName - Feature name
|
|
187
|
+
* @returns {Object|null} Feature object or null
|
|
188
|
+
*/
|
|
189
|
+
export function getFeature(featureName) {
|
|
190
|
+
const state = loadState();
|
|
191
|
+
return state.features[featureName] || null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Create or get feature with default structure
|
|
196
|
+
* @param {string} featureName - Feature name
|
|
197
|
+
* @param {Object} options - Options
|
|
198
|
+
* @param {string} options.userRequest - User's feature request for workflow detection
|
|
199
|
+
* @param {string} options.workflow - Manual workflow override
|
|
200
|
+
* @param {string} options.projectPath - Project path
|
|
201
|
+
* @returns {Promise<Object>} Feature object
|
|
202
|
+
*/
|
|
203
|
+
async function ensureFeature(featureName, options = {}) {
|
|
204
|
+
const state = loadState();
|
|
205
|
+
|
|
206
|
+
if (!state.features[featureName]) {
|
|
207
|
+
// Detect workflow if userRequest provided
|
|
208
|
+
let workflowId = 'auto';
|
|
209
|
+
let workflowDetection = {
|
|
210
|
+
auto: false,
|
|
211
|
+
confidence: 0,
|
|
212
|
+
userOverride: false
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (options.workflow) {
|
|
216
|
+
// Manual override
|
|
217
|
+
workflowId = options.workflow;
|
|
218
|
+
workflowDetection.userOverride = true;
|
|
219
|
+
} else if (options.userRequest) {
|
|
220
|
+
// Auto-detect
|
|
221
|
+
try {
|
|
222
|
+
const detection = await detectWorkflow({
|
|
223
|
+
userRequest: options.userRequest,
|
|
224
|
+
projectPath: options.projectPath || '.',
|
|
225
|
+
featureName
|
|
226
|
+
});
|
|
227
|
+
workflowId = detection.workflowId;
|
|
228
|
+
workflowDetection.auto = true;
|
|
229
|
+
workflowDetection.confidence = detection.confidence;
|
|
230
|
+
workflowDetection.matchedKeywords = detection.matchedKeywords;
|
|
231
|
+
workflowDetection.estimatedComplexity = detection.estimatedComplexity;
|
|
232
|
+
workflowDetection.reasoning = detection.reasoning;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
// If detection fails, fall back to auto
|
|
235
|
+
console.warn('Workflow detection failed:', err.message);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
state.features[featureName] = {
|
|
240
|
+
status: "draft",
|
|
241
|
+
workflow: workflowId, // auto | fast-track | standard | full-morph | design-impl | ui-refresh
|
|
242
|
+
workflowDetection,
|
|
243
|
+
createdAt: new Date().toISOString(),
|
|
244
|
+
updatedAt: new Date().toISOString(),
|
|
245
|
+
activeAgents: [],
|
|
246
|
+
approvalGates: {
|
|
247
|
+
proposal: { approved: false, timestamp: null, approvedBy: null },
|
|
248
|
+
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
249
|
+
design: { approved: false, timestamp: null, approvedBy: null },
|
|
250
|
+
tasks: { approved: false, timestamp: null, approvedBy: null }
|
|
251
|
+
},
|
|
252
|
+
tasks: {
|
|
253
|
+
total: 0,
|
|
254
|
+
completed: 0,
|
|
255
|
+
inProgress: 0,
|
|
256
|
+
pending: 0
|
|
257
|
+
},
|
|
258
|
+
checkpoints: [],
|
|
259
|
+
threadMetrics: {
|
|
260
|
+
totalThreads: 0,
|
|
261
|
+
parallelPeak: 0,
|
|
262
|
+
avgDuration: 0,
|
|
263
|
+
checkpointPassRate: 100
|
|
264
|
+
},
|
|
265
|
+
trustConfig: {
|
|
266
|
+
level: 'low',
|
|
267
|
+
history: [],
|
|
268
|
+
autoApprove: {
|
|
269
|
+
design: false,
|
|
270
|
+
tasks: false
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
contextBundles: [],
|
|
274
|
+
fileChanges: []
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
state.metadata.totalFeatures++;
|
|
278
|
+
saveState(state);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return state.features[featureName];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Update feature property (supports nested keys like "tasks.completed")
|
|
286
|
+
* @param {string} featureName - Feature name
|
|
287
|
+
* @param {string} key - Property key (supports dot notation)
|
|
288
|
+
* @param {any} value - Value to set
|
|
289
|
+
*/
|
|
290
|
+
export async function updateFeature(featureName, key, value) {
|
|
291
|
+
await ensureFeature(featureName);
|
|
292
|
+
const state = loadState(); // Load AFTER ensuring feature exists
|
|
293
|
+
|
|
294
|
+
const keys = key.split('.');
|
|
295
|
+
let target = state.features[featureName];
|
|
296
|
+
|
|
297
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
298
|
+
if (!target[keys[i]]) {
|
|
299
|
+
target[keys[i]] = {};
|
|
300
|
+
}
|
|
301
|
+
target = target[keys[i]];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const finalKey = keys[keys.length - 1];
|
|
305
|
+
target[finalKey] = value;
|
|
306
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
307
|
+
|
|
308
|
+
saveState(state);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Update multiple feature properties at once
|
|
313
|
+
* @param {string} featureName - Feature name
|
|
314
|
+
* @param {Object} updates - Object with key-value pairs to update
|
|
315
|
+
*/
|
|
316
|
+
export async function updateFeatureMultiple(featureName, updates) {
|
|
317
|
+
await ensureFeature(featureName);
|
|
318
|
+
const state = loadState(); // Load AFTER ensuring feature exists
|
|
319
|
+
|
|
320
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
321
|
+
const keys = key.split('.');
|
|
322
|
+
let target = state.features[featureName];
|
|
323
|
+
|
|
324
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
325
|
+
if (!target[keys[i]]) {
|
|
326
|
+
target[keys[i]] = {};
|
|
327
|
+
}
|
|
328
|
+
target = target[keys[i]];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const finalKey = keys[keys.length - 1];
|
|
332
|
+
target[finalKey] = value;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
336
|
+
saveState(state);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Add checkpoint to feature
|
|
341
|
+
* @param {string} featureName - Feature name
|
|
342
|
+
* @param {string} note - Checkpoint note
|
|
343
|
+
* @returns {Object} Checkpoint object
|
|
344
|
+
*/
|
|
345
|
+
export async function addCheckpoint(featureName, note) {
|
|
346
|
+
await ensureFeature(featureName);
|
|
347
|
+
const state = loadState();
|
|
348
|
+
const feature = state.features[featureName];
|
|
349
|
+
|
|
350
|
+
const checkpoint = {
|
|
351
|
+
passed: true,
|
|
352
|
+
checkpointNum: feature.checkpoints.length + 1,
|
|
353
|
+
timestamp: new Date().toISOString(),
|
|
354
|
+
phase: feature.phase || 'setup',
|
|
355
|
+
results: [],
|
|
356
|
+
summary: {
|
|
357
|
+
note: note,
|
|
358
|
+
completedTasks: feature.tasks.completed
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
feature.checkpoints.push(checkpoint);
|
|
363
|
+
feature.updatedAt = new Date().toISOString();
|
|
364
|
+
|
|
365
|
+
saveState(state);
|
|
366
|
+
return checkpoint;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Add agent to feature
|
|
371
|
+
* @param {string} featureName - Feature name
|
|
372
|
+
* @param {string} agentId - Agent ID
|
|
373
|
+
* @returns {boolean} True if added, false if already exists
|
|
374
|
+
*/
|
|
375
|
+
export async function addAgent(featureName, agentId) {
|
|
376
|
+
await ensureFeature(featureName);
|
|
377
|
+
const state = loadState(); // Load AFTER ensuring feature exists
|
|
378
|
+
|
|
379
|
+
if (!state.features[featureName].activeAgents.includes(agentId)) {
|
|
380
|
+
state.features[featureName].activeAgents.push(agentId);
|
|
381
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
382
|
+
saveState(state);
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Remove agent from feature
|
|
391
|
+
* @param {string} featureName - Feature name
|
|
392
|
+
* @param {string} agentId - Agent ID
|
|
393
|
+
* @returns {boolean} True if removed, false if not found
|
|
394
|
+
*/
|
|
395
|
+
export function removeAgent(featureName, agentId) {
|
|
396
|
+
const state = loadState();
|
|
397
|
+
|
|
398
|
+
if (!state.features[featureName]) {
|
|
399
|
+
throw new Error(`Feature '${featureName}' not found.`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const index = state.features[featureName].activeAgents.indexOf(agentId);
|
|
403
|
+
if (index > -1) {
|
|
404
|
+
state.features[featureName].activeAgents.splice(index, 1);
|
|
405
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
406
|
+
saveState(state);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Normalize output type from kebab-case to camelCase (BUG-002 fix - Enhanced)
|
|
415
|
+
* Supports both kebab-case and camelCase input formats for all output types
|
|
416
|
+
* @param {string} type - Output type (e.g., 'ui-design-system', 'schema-analysis', or 'uiDesignSystem')
|
|
417
|
+
* @returns {string} Normalized type in camelCase
|
|
418
|
+
*/
|
|
419
|
+
function normalizeOutputType(type) {
|
|
420
|
+
// If string doesn't contain hyphens, assume it's already camelCase
|
|
421
|
+
if (!type.includes('-')) {
|
|
422
|
+
return type;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Convert kebab-case to camelCase automatically
|
|
426
|
+
// Example: 'ui-design-system' → 'uiDesignSystem'
|
|
427
|
+
// Example: 'schema-analysis' → 'schemaAnalysis'
|
|
428
|
+
const camelCase = type.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
429
|
+
|
|
430
|
+
return camelCase;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Calculate Levenshtein distance between two strings
|
|
435
|
+
* Used for fuzzy matching and typo detection
|
|
436
|
+
* @param {string} a - First string
|
|
437
|
+
* @param {string} b - Second string
|
|
438
|
+
* @returns {number} Edit distance
|
|
439
|
+
*/
|
|
440
|
+
function levenshteinDistance(a, b) {
|
|
441
|
+
const matrix = [];
|
|
442
|
+
|
|
443
|
+
for (let i = 0; i <= b.length; i++) {
|
|
444
|
+
matrix[i] = [i];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
for (let j = 0; j <= a.length; j++) {
|
|
448
|
+
matrix[0][j] = j;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
for (let i = 1; i <= b.length; i++) {
|
|
452
|
+
for (let j = 1; j <= a.length; j++) {
|
|
453
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
454
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
455
|
+
} else {
|
|
456
|
+
matrix[i][j] = Math.min(
|
|
457
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
458
|
+
matrix[i][j - 1] + 1, // insertion
|
|
459
|
+
matrix[i - 1][j] + 1 // deletion
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return matrix[b.length][a.length];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Find closest matching output type for error suggestions
|
|
470
|
+
* @param {string} inputType - The invalid input type
|
|
471
|
+
* @param {Array<string>} validTypes - List of valid output types
|
|
472
|
+
* @returns {string|null} Closest match or null
|
|
473
|
+
*/
|
|
474
|
+
function findClosestMatch(inputType, validTypes) {
|
|
475
|
+
let closestMatch = null;
|
|
476
|
+
let minDistance = Infinity;
|
|
477
|
+
|
|
478
|
+
const normalizedInput = inputType.toLowerCase();
|
|
479
|
+
|
|
480
|
+
for (const validType of validTypes) {
|
|
481
|
+
const distance = levenshteinDistance(normalizedInput, validType.toLowerCase());
|
|
482
|
+
|
|
483
|
+
// Consider it a match if distance is less than 3 (likely typo)
|
|
484
|
+
// or if one string contains the other (partial match)
|
|
485
|
+
if (distance < minDistance && distance <= 3) {
|
|
486
|
+
minDistance = distance;
|
|
487
|
+
closestMatch = validType;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Also check for partial matches
|
|
491
|
+
if (validType.toLowerCase().includes(normalizedInput) ||
|
|
492
|
+
normalizedInput.includes(validType.toLowerCase())) {
|
|
493
|
+
if (distance < minDistance) {
|
|
494
|
+
minDistance = distance;
|
|
495
|
+
closestMatch = validType;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Only suggest if similarity is reasonable
|
|
501
|
+
return minDistance <= 3 ? closestMatch : null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Mark output as created
|
|
506
|
+
* @param {string} featureName - Feature name
|
|
507
|
+
* @param {string} outputType - Output type (proposal, spec, contracts, etc.)
|
|
508
|
+
*/
|
|
509
|
+
export async function markOutput(featureName, outputType) {
|
|
510
|
+
await ensureFeature(featureName);
|
|
511
|
+
const state = loadState();
|
|
512
|
+
|
|
513
|
+
const normalized = normalizeOutputType(outputType);
|
|
514
|
+
|
|
515
|
+
// Initialize outputs on demand (not pre-stored since v5.0.0)
|
|
516
|
+
if (!state.features[featureName].outputs) {
|
|
517
|
+
state.features[featureName].outputs = getAllOutputPaths(featureName);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Define all valid output types (both camelCase and kebab-case alternatives)
|
|
521
|
+
const validTypes = [
|
|
522
|
+
'proposal', 'spec', 'contracts', 'tasks', 'decisions', 'recap',
|
|
523
|
+
'uiDesignSystem', 'ui-design-system',
|
|
524
|
+
'uiMockups', 'ui-mockups',
|
|
525
|
+
'uiComponents', 'ui-components',
|
|
526
|
+
'uiFlows', 'ui-flows'
|
|
527
|
+
];
|
|
528
|
+
|
|
529
|
+
if (!state.features[featureName].outputs[normalized]) {
|
|
530
|
+
// Try to find closest match for better error message
|
|
531
|
+
const validCamelCaseTypes = Object.keys(state.features[featureName].outputs);
|
|
532
|
+
const suggestion = findClosestMatch(outputType, validCamelCaseTypes);
|
|
533
|
+
|
|
534
|
+
let errorMsg = `Output type '${outputType}' not valid.`;
|
|
535
|
+
|
|
536
|
+
if (suggestion) {
|
|
537
|
+
errorMsg += `\n\nDid you mean '${suggestion}'?`;
|
|
538
|
+
// If the suggestion has a kebab-case alternative, show it too
|
|
539
|
+
const kebabAlternative = {
|
|
540
|
+
'uiDesignSystem': 'ui-design-system',
|
|
541
|
+
'uiMockups': 'ui-mockups',
|
|
542
|
+
'uiComponents': 'ui-components',
|
|
543
|
+
'uiFlows': 'ui-flows'
|
|
544
|
+
}[suggestion];
|
|
545
|
+
if (kebabAlternative) {
|
|
546
|
+
errorMsg += ` (also accepts '${kebabAlternative}')`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
errorMsg += `\n\nValid types:\n`;
|
|
551
|
+
errorMsg += ` - Standard: proposal, spec, contracts, tasks, decisions, recap\n`;
|
|
552
|
+
errorMsg += ` - UI/UX: uiDesignSystem, uiMockups, uiComponents, uiFlows\n`;
|
|
553
|
+
errorMsg += `\nNote: UI types also accept kebab-case (e.g., 'ui-design-system')`;
|
|
554
|
+
|
|
555
|
+
throw new Error(errorMsg);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
state.features[featureName].outputs[normalized].created = true;
|
|
559
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
560
|
+
|
|
561
|
+
// Track the file change automatically
|
|
562
|
+
const outputPath = state.features[featureName].outputs[normalized].path;
|
|
563
|
+
if (outputPath) {
|
|
564
|
+
if (!Array.isArray(state.features[featureName].fileChanges)) {
|
|
565
|
+
state.features[featureName].fileChanges = [];
|
|
566
|
+
}
|
|
567
|
+
state.features[featureName].fileChanges.push({
|
|
568
|
+
path: outputPath,
|
|
569
|
+
action: 'created',
|
|
570
|
+
phase: state.features[featureName].phase,
|
|
571
|
+
timestamp: new Date().toISOString()
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// If marking tasks output, try to sync task count from state tasks array
|
|
576
|
+
if (normalized === 'tasks') {
|
|
577
|
+
syncTasksCount(state.features[featureName]);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
saveState(state);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Sync progress counters from taskList array into feature.progress
|
|
585
|
+
* @param {Object} feature - Feature object
|
|
586
|
+
*/
|
|
587
|
+
function syncTasksCount(feature) {
|
|
588
|
+
const list = feature.taskList;
|
|
589
|
+
if (!Array.isArray(list) || list.length === 0) return;
|
|
590
|
+
const completed = list.filter(t => t.status === 'completed').length;
|
|
591
|
+
feature.progress = {
|
|
592
|
+
total: list.length,
|
|
593
|
+
completed,
|
|
594
|
+
inProgress: list.filter(t => t.status === 'in_progress').length,
|
|
595
|
+
pending: list.filter(t => t.status === 'pending').length,
|
|
596
|
+
percentage: Math.round((completed / list.length) * 100)
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Track a file change for a feature
|
|
602
|
+
* @param {string} featureName - Feature name
|
|
603
|
+
* @param {string} filePath - Path of the changed file
|
|
604
|
+
* @param {string} action - 'created' | 'modified' | 'deleted'
|
|
605
|
+
* @param {string} phase - Phase during which the change occurred
|
|
606
|
+
*/
|
|
607
|
+
export async function trackFileChange(featureName, filePath, action, phase) {
|
|
608
|
+
await ensureFeature(featureName);
|
|
609
|
+
const state = loadState();
|
|
610
|
+
|
|
611
|
+
if (!Array.isArray(state.features[featureName].fileChanges)) {
|
|
612
|
+
state.features[featureName].fileChanges = [];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
state.features[featureName].fileChanges.push({
|
|
616
|
+
path: filePath,
|
|
617
|
+
action,
|
|
618
|
+
phase: phase || state.features[featureName].phase || 'setup',
|
|
619
|
+
timestamp: new Date().toISOString()
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
623
|
+
saveState(state);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Get file changes for a feature, optionally grouped by phase
|
|
628
|
+
* @param {string} featureName - Feature name
|
|
629
|
+
* @param {Object} options - Options
|
|
630
|
+
* @param {boolean} options.groupByPhase - Group changes by phase
|
|
631
|
+
* @returns {Array|Object} File changes
|
|
632
|
+
*/
|
|
633
|
+
export function getFileChanges(featureName, options = {}) {
|
|
634
|
+
const state = loadState();
|
|
635
|
+
const feature = state.features[featureName];
|
|
636
|
+
|
|
637
|
+
if (!feature) {
|
|
638
|
+
throw new Error(`Feature '${featureName}' not found.`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const changes = feature.fileChanges || [];
|
|
642
|
+
|
|
643
|
+
if (options.groupByPhase) {
|
|
644
|
+
const grouped = {};
|
|
645
|
+
for (const change of changes) {
|
|
646
|
+
const phase = change.phase || 'unknown';
|
|
647
|
+
if (!grouped[phase]) grouped[phase] = [];
|
|
648
|
+
grouped[phase].push(change);
|
|
649
|
+
}
|
|
650
|
+
return grouped;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return changes;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Skip a phase and record it
|
|
658
|
+
* @param {string} featureName - Feature name
|
|
659
|
+
* @param {string} phase - Phase to skip
|
|
660
|
+
* @param {string} reason - Reason for skipping
|
|
661
|
+
*/
|
|
662
|
+
export async function skipPhase(featureName, phase, reason = '') {
|
|
663
|
+
await ensureFeature(featureName);
|
|
664
|
+
const state = loadState();
|
|
665
|
+
|
|
666
|
+
if (!state.features[featureName].skippedPhases) {
|
|
667
|
+
state.features[featureName].skippedPhases = [];
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Don't add duplicates
|
|
671
|
+
const existing = state.features[featureName].skippedPhases.find(s => s.phase === phase);
|
|
672
|
+
if (!existing) {
|
|
673
|
+
state.features[featureName].skippedPhases.push({
|
|
674
|
+
phase,
|
|
675
|
+
reason,
|
|
676
|
+
timestamp: new Date().toISOString()
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
681
|
+
saveState(state);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Get skipped phases for a feature
|
|
686
|
+
* @param {string} featureName - Feature name
|
|
687
|
+
* @returns {Array} Array of skipped phases
|
|
688
|
+
*/
|
|
689
|
+
export function getSkippedPhases(featureName) {
|
|
690
|
+
const state = loadState();
|
|
691
|
+
if (!state.features[featureName]) {
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
return state.features[featureName].skippedPhases || [];
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* List all features
|
|
699
|
+
* @returns {Array} Array of [featureName, featureObject] tuples
|
|
700
|
+
*/
|
|
701
|
+
export function listFeatures() {
|
|
702
|
+
const state = loadState();
|
|
703
|
+
return Object.entries(state.features);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Get project summary
|
|
708
|
+
* @returns {Object} Summary with metadata
|
|
709
|
+
*/
|
|
710
|
+
export function getSummary() {
|
|
711
|
+
const state = loadState();
|
|
712
|
+
return {
|
|
713
|
+
project: state.project,
|
|
714
|
+
metadata: state.metadata,
|
|
715
|
+
featuresCount: Object.keys(state.features).length
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// ============================================================================
|
|
720
|
+
// Approval Gates Operations
|
|
721
|
+
// ============================================================================
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Set approval gate status
|
|
725
|
+
* @param {string} featureName - Feature name
|
|
726
|
+
* @param {string} gate - Gate name (proposal, uiux, design, tasks)
|
|
727
|
+
* @param {boolean} approved - Approval status
|
|
728
|
+
* @param {Object} metadata - Additional metadata (approvedBy, reason, etc.)
|
|
729
|
+
*/
|
|
730
|
+
export async function setApprovalGate(featureName, gate, approved, metadata = {}) {
|
|
731
|
+
const state = loadState();
|
|
732
|
+
const feature = await ensureFeature(featureName);
|
|
733
|
+
|
|
734
|
+
if (!feature.approvalGates) {
|
|
735
|
+
feature.approvalGates = {
|
|
736
|
+
proposal: { approved: false, timestamp: null, approvedBy: null },
|
|
737
|
+
uiux: { approved: false, timestamp: null, approvedBy: null },
|
|
738
|
+
design: { approved: false, timestamp: null, approvedBy: null },
|
|
739
|
+
tasks: { approved: false, timestamp: null, approvedBy: null }
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
feature.approvalGates[gate] = {
|
|
744
|
+
approved,
|
|
745
|
+
timestamp: metadata.approvedAt || metadata.rejectedAt || new Date().toISOString(),
|
|
746
|
+
approvedBy: metadata.approvedBy || metadata.rejectedBy || null,
|
|
747
|
+
...metadata
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
state.features[featureName] = feature;
|
|
751
|
+
saveState(state);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Get approval gate status
|
|
756
|
+
* @param {string} featureName - Feature name
|
|
757
|
+
* @param {string} gate - Gate name
|
|
758
|
+
* @returns {Object|null} Gate object or null
|
|
759
|
+
*/
|
|
760
|
+
export function getApprovalGate(featureName, gate) {
|
|
761
|
+
const state = loadState();
|
|
762
|
+
const feature = state.features[featureName];
|
|
763
|
+
|
|
764
|
+
if (!feature || !feature.approvalGates) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return feature.approvalGates[gate] || null;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Check if feature is pending approval
|
|
773
|
+
* @param {string} featureName - Feature name
|
|
774
|
+
* @returns {boolean} True if any gate is pending approval
|
|
775
|
+
*/
|
|
776
|
+
export function isPendingApproval(featureName) {
|
|
777
|
+
const state = loadState();
|
|
778
|
+
const feature = state.features[featureName];
|
|
779
|
+
|
|
780
|
+
if (!feature || !feature.approvalGates) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const currentPhase = feature.phase || 'setup';
|
|
785
|
+
|
|
786
|
+
// Check if current phase has an approval gate
|
|
787
|
+
const phaseGateMap = {
|
|
788
|
+
'design': 'design',
|
|
789
|
+
'tasks': 'tasks',
|
|
790
|
+
'uiux': 'uiux'
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const relevantGate = phaseGateMap[currentPhase];
|
|
794
|
+
if (!relevantGate) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const gate = feature.approvalGates[relevantGate];
|
|
799
|
+
return gate && !gate.approved;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Get approval history for a feature
|
|
804
|
+
* @param {string} featureName - Feature name
|
|
805
|
+
* @returns {Array} Array of approval events
|
|
806
|
+
*/
|
|
807
|
+
export function getApprovalHistory(featureName) {
|
|
808
|
+
const state = loadState();
|
|
809
|
+
const feature = state.features[featureName];
|
|
810
|
+
|
|
811
|
+
if (!feature || !feature.approvalGates) {
|
|
812
|
+
return [];
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const history = [];
|
|
816
|
+
|
|
817
|
+
Object.entries(feature.approvalGates).forEach(([gate, data]) => {
|
|
818
|
+
if (data.timestamp) {
|
|
819
|
+
history.push({
|
|
820
|
+
gate,
|
|
821
|
+
approved: data.approved,
|
|
822
|
+
timestamp: data.timestamp,
|
|
823
|
+
approvedBy: data.approvedBy || data.rejectedBy,
|
|
824
|
+
reason: data.reason
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Sort by timestamp
|
|
830
|
+
return history.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
831
|
+
}
|