sinapse-ai 1.16.0 → 1.19.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/hooks/doc-first-gate.cjs +156 -0
- package/.claude/hooks/enforce-story-gate.cjs +6 -2
- package/.claude/rules/documentation-first.md +6 -0
- package/.claude/rules/hook-governance.md +1 -0
- package/.sinapse-ai/constitution.md +3 -3
- package/.sinapse-ai/core/atlas/atlas-data.js +278 -0
- package/.sinapse-ai/core/atlas/flows-pt.js +253 -0
- package/.sinapse-ai/core/atlas/flows.js +266 -0
- package/.sinapse-ai/core/atlas/index.js +62 -0
- package/.sinapse-ai/core/atlas/render-html.js +216 -0
- package/.sinapse-ai/core/atlas/render-markdown.js +313 -0
- package/.sinapse-ai/core/atlas/render-research-card.js +164 -0
- package/.sinapse-ai/core/code-intel/registry-syncer.js +5 -3
- package/.sinapse-ai/core/doctor/checks/agent-memory.js +7 -5
- package/.sinapse-ai/core/doctor/checks/claude-md.js +13 -0
- package/.sinapse-ai/core/doctor/checks/constitution-consistency.js +1 -0
- package/.sinapse-ai/core/doctor/checks/hooks-claude-count.js +11 -0
- package/.sinapse-ai/core/doctor/checks/settings-json.js +16 -2
- package/.sinapse-ai/core/doctor/index.js +27 -0
- package/.sinapse-ai/core/execution/build-orchestrator.js +192 -8
- package/.sinapse-ai/core/external-executors/delegate-cli.js +6 -2
- package/.sinapse-ai/core/health-check/checks/project/constitution-consistency.js +2 -1
- package/.sinapse-ai/core/ideation/ideation-engine.js +10 -8
- package/.sinapse-ai/core/mcp/config-migrator.js +8 -5
- package/.sinapse-ai/core/mcp/global-config-manager.js +4 -1
- package/.sinapse-ai/core/mcp/symlink-manager.js +6 -3
- package/.sinapse-ai/core/orchestration/brownfield-handler.js +5 -4
- package/.sinapse-ai/core/orchestration/build-command.js +136 -0
- package/.sinapse-ai/core/orchestration/doc-first-resolver.js +322 -0
- package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +3 -0
- package/.sinapse-ai/core/orchestration/gate-evaluator.js +58 -9
- package/.sinapse-ai/core/orchestration/route-command.js +97 -0
- package/.sinapse-ai/core/orchestration/session-state.js +19 -2
- package/.sinapse-ai/core/orchestration/terminal-spawner.js +24 -1
- package/.sinapse-ai/core/orchestration/workflow-executor.js +102 -3
- package/.sinapse-ai/core/quality-gates/focus-area-recommender.js +15 -11
- package/.sinapse-ai/core/registry/build-registry.js +3 -2
- package/.sinapse-ai/core/synapse/scripts/generate-constitution.js +1 -0
- package/.sinapse-ai/core/synapse/utils/atomic-write.js +34 -9
- package/.sinapse-ai/core-config.yaml +5 -1
- package/.sinapse-ai/data/capability-detection.js +3 -3
- package/.sinapse-ai/data/entity-registry.yaml +288 -90
- package/.sinapse-ai/data/mcp-discipline.js +1 -1
- package/.sinapse-ai/data/mcp-tool-examples.yaml +8 -8
- package/.sinapse-ai/data/tok2-validation.js +1 -1
- package/.sinapse-ai/data/tool-registry.yaml +4 -3
- package/.sinapse-ai/development/agents/snps-orqx.md +14 -5
- package/.sinapse-ai/development/scripts/migrate-task-to-v2.js +1 -1
- package/.sinapse-ai/development/scripts/validate-task-v2.js +67 -4
- package/.sinapse-ai/development/tasks/advanced-elicitation.md +3 -3
- package/.sinapse-ai/development/tasks/analyst-facilitate-brainstorming.md +3 -3
- package/.sinapse-ai/development/tasks/analyze-framework.md +1 -8
- package/.sinapse-ai/development/tasks/analyze-performance.md +1 -8
- package/.sinapse-ai/development/tasks/apply-qa-fixes.md +3 -3
- package/.sinapse-ai/development/tasks/architect-analyze-impact.md +3 -3
- package/.sinapse-ai/development/tasks/audit-codebase.md +1 -8
- package/.sinapse-ai/development/tasks/audit-tailwind-config.md +1 -4
- package/.sinapse-ai/development/tasks/audit-utilities.md +1 -8
- package/.sinapse-ai/development/tasks/bootstrap-shadcn-library.md +3 -3
- package/.sinapse-ai/development/tasks/brownfield-create-epic.md +3 -3
- package/.sinapse-ai/development/tasks/brownfield-create-story.md +3 -3
- package/.sinapse-ai/development/tasks/build-component.md +2 -5
- package/.sinapse-ai/development/tasks/calculate-roi.md +3 -3
- package/.sinapse-ai/development/tasks/ci-cd-configuration.md +1 -1
- package/.sinapse-ai/development/tasks/cleanup-utilities.md +3 -3
- package/.sinapse-ai/development/tasks/collaborative-edit.md +3 -3
- package/.sinapse-ai/development/tasks/compose-molecule.md +2 -5
- package/.sinapse-ai/development/tasks/consolidate-patterns.md +3 -3
- package/.sinapse-ai/development/tasks/correct-course.md +3 -3
- package/.sinapse-ai/development/tasks/create-brownfield-story.md +2 -5
- package/.sinapse-ai/development/tasks/create-deep-research-prompt.md +2 -5
- package/.sinapse-ai/development/tasks/create-doc.md +3 -6
- package/.sinapse-ai/development/tasks/create-next-story.md +4 -8
- package/.sinapse-ai/development/tasks/create-suite.md +2 -5
- package/.sinapse-ai/development/tasks/create-task.md +2 -5
- package/.sinapse-ai/development/tasks/create-workflow.md +2 -5
- package/.sinapse-ai/development/tasks/db-analyze-hotpaths.md +1 -8
- package/.sinapse-ai/development/tasks/db-apply-migration.md +1 -8
- package/.sinapse-ai/development/tasks/db-bootstrap.md +1 -8
- package/.sinapse-ai/development/tasks/db-domain-modeling.md +1 -8
- package/.sinapse-ai/development/tasks/db-dry-run.md +1 -8
- package/.sinapse-ai/development/tasks/db-env-check.md +1 -8
- package/.sinapse-ai/development/tasks/db-explain.md +1 -8
- package/.sinapse-ai/development/tasks/db-impersonate.md +1 -8
- package/.sinapse-ai/development/tasks/db-load-csv.md +1 -8
- package/.sinapse-ai/development/tasks/db-policy-apply.md +1 -8
- package/.sinapse-ai/development/tasks/db-rls-audit.md +1 -8
- package/.sinapse-ai/development/tasks/db-rollback.md +1 -8
- package/.sinapse-ai/development/tasks/db-run-sql.md +1 -8
- package/.sinapse-ai/development/tasks/db-schema-audit.md +1 -8
- package/.sinapse-ai/development/tasks/db-seed.md +1 -8
- package/.sinapse-ai/development/tasks/db-smoke-test.md +1 -8
- package/.sinapse-ai/development/tasks/db-snapshot.md +1 -8
- package/.sinapse-ai/development/tasks/db-squad-integration.md +1 -8
- package/.sinapse-ai/development/tasks/db-supabase-setup.md +1 -8
- package/.sinapse-ai/development/tasks/db-verify-order.md +1 -8
- package/.sinapse-ai/development/tasks/deprecate-component.md +3 -3
- package/.sinapse-ai/development/tasks/dev-apply-qa-fixes.md +3 -3
- package/.sinapse-ai/development/tasks/dev-backlog-debt.md +3 -3
- package/.sinapse-ai/development/tasks/dev-develop-story.md +1 -1
- package/.sinapse-ai/development/tasks/dev-improve-code-quality.md +3 -3
- package/.sinapse-ai/development/tasks/dev-optimize-performance.md +3 -3
- package/.sinapse-ai/development/tasks/dev-suggest-refactoring.md +3 -3
- package/.sinapse-ai/development/tasks/dev-validate-next-story.md +3 -3
- package/.sinapse-ai/development/tasks/document-project.md +3 -3
- package/.sinapse-ai/development/tasks/environment-bootstrap.md +0 -4
- package/.sinapse-ai/development/tasks/execute-checklist.md +3 -3
- package/.sinapse-ai/development/tasks/export-design-tokens-dtcg.md +3 -3
- package/.sinapse-ai/development/tasks/extend-pattern.md +1 -8
- package/.sinapse-ai/development/tasks/extract-tokens.md +3 -3
- package/.sinapse-ai/development/tasks/facilitate-brainstorming-session.md +1 -1
- package/.sinapse-ai/development/tasks/generate-ai-frontend-prompt.md +2 -5
- package/.sinapse-ai/development/tasks/generate-documentation.md +2 -5
- package/.sinapse-ai/development/tasks/generate-migration-strategy.md +2 -5
- package/.sinapse-ai/development/tasks/generate-shock-report.md +2 -5
- package/.sinapse-ai/development/tasks/github-devops-github-pr-automation.md +3 -3
- package/.sinapse-ai/development/tasks/github-devops-repository-cleanup.md +3 -3
- package/.sinapse-ai/development/tasks/github-devops-version-management.md +3 -3
- package/.sinapse-ai/development/tasks/improve-self.md +1 -16
- package/.sinapse-ai/development/tasks/index-docs.md +1 -4
- package/.sinapse-ai/development/tasks/init-project-status.md +1 -7
- package/.sinapse-ai/development/tasks/integrate-squad.md +3 -3
- package/.sinapse-ai/development/tasks/kb-mode-interaction.md +3 -3
- package/.sinapse-ai/development/tasks/learn-patterns.md +3 -3
- package/.sinapse-ai/development/tasks/modify-agent.md +1 -8
- package/.sinapse-ai/development/tasks/modify-task.md +1 -8
- package/.sinapse-ai/development/tasks/modify-workflow.md +1 -8
- package/.sinapse-ai/development/tasks/po-backlog-add.md +3 -3
- package/.sinapse-ai/development/tasks/po-manage-story-backlog.md +3 -3
- package/.sinapse-ai/development/tasks/po-pull-story-from-clickup.md +3 -3
- package/.sinapse-ai/development/tasks/po-pull-story.md +3 -3
- package/.sinapse-ai/development/tasks/po-stories-index.md +3 -3
- package/.sinapse-ai/development/tasks/po-sync-story-to-clickup.md +3 -3
- package/.sinapse-ai/development/tasks/po-sync-story.md +3 -3
- package/.sinapse-ai/development/tasks/pr-automation.md +1 -1
- package/.sinapse-ai/development/tasks/propose-modification.md +3 -3
- package/.sinapse-ai/development/tasks/qa-backlog-add-followup.md +1 -8
- package/.sinapse-ai/development/tasks/qa-create-fix-request.md +1 -4
- package/.sinapse-ai/development/tasks/qa-gate.md +1 -8
- package/.sinapse-ai/development/tasks/qa-generate-tests.md +1 -8
- package/.sinapse-ai/development/tasks/qa-nfr-assess.md +1 -8
- package/.sinapse-ai/development/tasks/qa-review-proposal.md +1 -8
- package/.sinapse-ai/development/tasks/qa-review-story.md +1 -8
- package/.sinapse-ai/development/tasks/qa-risk-profile.md +1 -8
- package/.sinapse-ai/development/tasks/qa-run-tests.md +1 -8
- package/.sinapse-ai/development/tasks/qa-test-design.md +1 -8
- package/.sinapse-ai/development/tasks/qa-trace-requirements.md +1 -8
- package/.sinapse-ai/development/tasks/release-management.md +1 -1
- package/.sinapse-ai/development/tasks/security-audit.md +1 -4
- package/.sinapse-ai/development/tasks/security-scan.md +1 -4
- package/.sinapse-ai/development/tasks/setup-database.md +2 -11
- package/.sinapse-ai/development/tasks/setup-design-system.md +2 -11
- package/.sinapse-ai/development/tasks/shard-doc.md +3 -3
- package/.sinapse-ai/development/tasks/sm-create-next-story.md +3 -3
- package/.sinapse-ai/development/tasks/spec-gather-requirements.md +11 -0
- package/.sinapse-ai/development/tasks/sync-documentation.md +1 -6
- package/.sinapse-ai/development/tasks/tailwind-upgrade.md +3 -3
- package/.sinapse-ai/development/tasks/test-as-user.md +3 -3
- package/.sinapse-ai/development/tasks/undo-last.md +2 -5
- package/.sinapse-ai/development/tasks/update-manifest.md +1 -8
- package/.sinapse-ai/development/tasks/ux-create-wireframe.md +1 -1
- package/.sinapse-ai/development/tasks/ux-ds-scan-artifact.md +3 -3
- package/.sinapse-ai/development/tasks/ux-user-research.md +3 -3
- package/.sinapse-ai/development/tasks/validate-next-story.md +1 -8
- package/.sinapse-ai/development/templates/approval-table.md +106 -0
- package/.sinapse-ai/development/templates/chrome-brain/scripts/chrome-ensure.cjs +1 -1
- package/.sinapse-ai/development/workflows/spec-pipeline.yaml +1 -0
- package/.sinapse-ai/git-hooks/post-commit +0 -0
- package/.sinapse-ai/git-hooks/pre-commit +0 -0
- package/.sinapse-ai/git-hooks/pre-push +0 -0
- package/.sinapse-ai/infrastructure/scripts/changelog-generator.js +5 -1
- package/.sinapse-ai/infrastructure/scripts/pattern-extractor.js +11 -2
- package/.sinapse-ai/infrastructure/scripts/test-utilities.js +34 -13
- package/.sinapse-ai/infrastructure/scripts/usage-analytics.js +9 -5
- package/.sinapse-ai/infrastructure/scripts/validate-claude-integration.js +10 -2
- package/.sinapse-ai/install-manifest.yaml +364 -320
- package/.sinapse-ai/lib/build.json +1 -0
- package/.sinapse-ai/product/templates/personalized-task-template-v2.md +4 -2
- package/.sinapse-ai/product/templates/prd-tmpl.yaml +6 -2
- package/.sinapse-ai/product/templates/statusline/statusline-script.js +7 -3
- package/CHANGELOG.md +85 -7
- package/README.en.md +5 -5
- package/README.md +5 -5
- package/bin/cli.js +1 -1
- package/bin/commands/install.js +288 -114
- package/bin/commands/local.js +11 -0
- package/bin/lib/command-generator.js +31 -2
- package/bin/sinapse.js +121 -0
- package/docs/README.md +1 -1
- package/docs/agent-reference-guide.md +1 -1
- package/docs/community/README-community-snippet-core.md +1 -1
- package/docs/community/README-community-snippet-mcp.md +1 -1
- package/docs/community/README-community-snippet-squads.md +1 -1
- package/docs/examples/quickstart-recording.md +1 -1
- package/docs/framework/README.md +1 -1
- package/docs/framework/architecture-overview.md +1 -0
- package/docs/framework/atlas/OPERATING-ATLAS.md +537 -0
- package/docs/framework/atlas/README.md +34 -0
- package/docs/framework/atlas/atlas-data.json +2810 -0
- package/docs/framework/atlas/atlas.html +392 -0
- package/docs/framework/coding-standards.md +1 -1
- package/docs/framework/core-architecture.md +1 -1
- package/docs/framework/guiding-principles.md +1 -1
- package/docs/framework/source-tree.md +1 -1
- package/docs/framework/tech-stack.md +1 -1
- package/docs/framework/versioning-and-releases.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/guides/agent-reference.md +1 -1
- package/docs/guides/agent-selection-guide.md +1 -1
- package/docs/guides/api-reference.md +1 -1
- package/docs/guides/contextual-greeting-system-guide.md +1 -1
- package/docs/guides/contributing-squads.md +1 -1
- package/docs/guides/development-setup.md +1 -1
- package/docs/guides/git-workflow-guide.md +1 -1
- package/docs/guides/installation-troubleshooting.md +1 -1
- package/docs/guides/llm-routing.md +1 -1
- package/docs/guides/mcp-global-setup.md +1 -1
- package/docs/guides/meta-agent-commands.md +1 -1
- package/docs/guides/project-status-feature.md +1 -1
- package/docs/guides/quality-dashboard.md +1 -1
- package/docs/guides/quality-gates.md +1 -1
- package/docs/guides/security-hardening.md +1 -1
- package/docs/guides/service-discovery.md +19 -22
- package/docs/guides/squad-examples/README.md +1 -1
- package/docs/guides/squad-migration.md +1 -1
- package/docs/guides/squads-guide.md +1 -1
- package/docs/guides/squads-overview.md +10 -13
- package/docs/guides/template-engine-v2.md +1 -1
- package/docs/guides/testing-guide.md +1 -1
- package/docs/guides/user-guide.md +30 -26
- package/docs/installation/README.md +3 -3
- package/docs/installation/faq.md +1 -1
- package/docs/installation/linux.md +2 -2
- package/docs/installation/macos.md +1 -1
- package/docs/installation/npx-install.md +1 -1
- package/docs/installation/troubleshooting.md +1 -1
- package/docs/installation/uninstallation.md +1 -1
- package/docs/installation/v4-quick-start.md +1 -1
- package/docs/installation/windows.md +2 -2
- package/docs/pt/CHANGELOG.md +1 -1
- package/docs/pt/DOCUMENTATION-ROADMAP.md +1 -1
- package/docs/pt/ENVIRONMENT.md +1 -1
- package/docs/pt/FEATURE_PROCESS.md +2 -2
- package/docs/pt/GUIDING-PRINCIPLES.md +2 -2
- package/docs/pt/agent-reference-guide.md +3 -3
- package/docs/pt/architecture/ARCHITECTURE-INDEX.md +2 -2
- package/docs/pt/architecture/ade-architecture.md +1 -1
- package/docs/pt/architecture/agent-config-audit.md +1 -1
- package/docs/pt/architecture/agent-responsibility-matrix.md +1 -1
- package/docs/pt/architecture/agent-tool-integration-guide.md +1 -1
- package/docs/pt/architecture/ci-cd.md +1 -1
- package/docs/pt/architecture/coding-standards.md +1 -1
- package/docs/pt/architecture/contribution-workflow-research.md +1 -1
- package/docs/pt/architecture/dashboard-architecture.md +1 -1
- package/docs/pt/architecture/dashboard-realtime.md +1 -1
- package/docs/pt/architecture/hcs-check-specifications.md +1 -1
- package/docs/pt/architecture/hcs-execution-modes.md +1 -1
- package/docs/pt/architecture/hcs-self-healing-spec.md +1 -1
- package/docs/pt/architecture/high-level-architecture.md +1 -1
- package/docs/pt/architecture/introduction.md +1 -1
- package/docs/pt/architecture/mcp-api-keys-management.md +3 -3
- package/docs/pt/architecture/mcp-system-diagrams.md +1 -1
- package/docs/pt/architecture/module-system.md +4 -4
- package/docs/pt/architecture/multi-repo-strategy.md +2 -2
- package/docs/pt/architecture/source-tree.md +1 -1
- package/docs/pt/architecture/squad-improvement-analysis.md +1 -1
- package/docs/pt/architecture/squad-improvement-recommended-approach.md +1 -1
- package/docs/pt/architecture/tech-stack.md +1 -1
- package/docs/pt/architecture/utility-integration-guide.md +1 -1
- package/docs/pt/code-of-conduct.md +1 -1
- package/docs/pt/community/README-community-snippet-core.md +1 -1
- package/docs/pt/community/README-community-snippet-mcp.md +2 -2
- package/docs/pt/contributing.md +7 -7
- package/docs/pt/core-architecture.md +1 -1
- package/docs/pt/docker-mcp-setup.md +1 -1
- package/docs/pt/framework/README.md +1 -1
- package/docs/pt/framework/coding-standards.md +1 -1
- package/docs/pt/framework/source-tree.md +1 -1
- package/docs/pt/framework/tech-stack.md +1 -1
- package/docs/pt/getting-started.md +1 -1
- package/docs/pt/git-workflow-guide.md +1 -1
- package/docs/pt/guides/README.md +3 -3
- package/docs/pt/guides/agent-selection-guide.md +2 -2
- package/docs/pt/guides/api-reference.md +1 -1
- package/docs/pt/guides/contextual-greeting-system-guide.md +1 -1
- package/docs/pt/guides/contributing-squads.md +1 -1
- package/docs/pt/guides/development-setup.md +1 -1
- package/docs/pt/guides/installation-troubleshooting.md +1 -1
- package/docs/pt/guides/llm-routing.md +1 -1
- package/docs/pt/guides/mcp/desktop-commander.md +1 -1
- package/docs/pt/guides/mcp/docker-gateway-tutorial.md +1 -1
- package/docs/pt/guides/mcp-global-setup.md +1 -1
- package/docs/pt/guides/project-status-feature.md +1 -1
- package/docs/pt/guides/quality-dashboard.md +1 -1
- package/docs/pt/guides/quality-gates.md +1 -1
- package/docs/pt/guides/security-hardening.md +2 -2
- package/docs/pt/guides/service-discovery.md +19 -22
- package/docs/pt/guides/squad-examples/README.md +1 -1
- package/docs/pt/guides/squad-migration.md +1 -1
- package/docs/pt/guides/squads-guide.md +1 -1
- package/docs/pt/guides/squads-overview.md +11 -13
- package/docs/pt/guides/template-engine-v2.md +1 -1
- package/docs/pt/guides/testing-guide.md +3 -3
- package/docs/pt/guides/user-guide.md +30 -26
- package/docs/pt/guides/workflows-guide.md +1 -1
- package/docs/pt/how-to-contribute-with-pull-requests.md +1 -1
- package/docs/pt/ide-integration.md +2 -2
- package/docs/pt/installation/README.md +1 -1
- package/docs/pt/installation/faq.md +1 -1
- package/docs/pt/installation/linux.md +1 -1
- package/docs/pt/installation/macos.md +1 -1
- package/docs/pt/installation/troubleshooting.md +1 -1
- package/docs/pt/installation/v4-quick-start.md +1 -1
- package/docs/pt/installation/windows.md +1 -1
- package/docs/pt/meta-agent-commands.md +1 -1
- package/docs/pt/migration-guide.md +1 -1
- package/docs/pt/npx-install.md +1 -1
- package/docs/pt/performance-tuning-guide.md +1 -1
- package/docs/pt/platforms/claude-code.md +2 -2
- package/docs/pt/roadmap.md +2 -2
- package/docs/pt/security-best-practices.md +2 -2
- package/docs/pt/security.md +1 -1
- package/docs/pt/sinapse-nomenclature-specification.md +1 -1
- package/docs/pt/troubleshooting.md +1 -1
- package/docs/pt/uninstallation.md +1 -1
- package/docs/pt/versioning-and-releases.md +1 -1
- package/docs/security/overview.md +1 -1
- package/docs/security/security-best-practices.md +2 -2
- package/docs/sinapse-workflows/README.md +1 -1
- package/package.json +2 -1
- package/packages/installer/package.json +1 -0
- package/packages/installer/src/wizard/feedback.js +18 -2
- package/packages/installer/src/wizard/i18n.js +78 -0
- package/packages/installer/src/wizard/ide-config-generator.js +14 -9
- package/packages/installer/src/wizard/index.js +16 -16
- package/packages/installer/src/wizard/validation/report-generator.js +22 -21
- package/packages/installer/src/wizard/validation/troubleshooting-system.js +19 -13
- package/scripts/gen-agent-badges.js +1 -1
- package/scripts/install-chrome-brain.sh +2 -2
- package/scripts/package-synapse.js +14 -5
- package/scripts/prepare-hooks.js +19 -0
- package/scripts/validate-agent-codenames.js +174 -0
- package/scripts/validate-all.js +1 -0
- package/scripts/validate-no-personal-leaks.js +24 -9
- package/sinapse/agents/sinapse-orqx.md +5 -5
- package/sinapse/agents/snps-orqx.md +5 -5
- package/squads/squad-brand/agents/brand-creative-engineer.md +5 -5
- package/squads/squad-brand/squad.yaml +6 -6
- package/squads/squad-content/agents/content-engineer.md +7 -7
- package/squads/squad-content/squad.yaml +5 -5
- package/squads/squad-copy/knowledge-base/consequence-headline-patterns.md +2 -2
- package/squads/squad-courses/agents/production-director.md +5 -5
- package/squads/squad-courses/squad.yaml +1 -1
- package/docs/guides/hooks-two-layers.md +0 -66
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
createGlobalConfig,
|
|
20
20
|
} = require('./global-config-manager');
|
|
21
21
|
const { getProjectMcpPath, checkLinkStatus, LINK_STATUS, createLink } = require('./symlink-manager');
|
|
22
|
+
const { retrySync } = require('../synapse/utils/atomic-write');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Migration options
|
|
@@ -267,7 +268,8 @@ function executeMigration(projectRoot = process.cwd(), option = MIGRATION_OPTION
|
|
|
267
268
|
const backupPath = projectMcpPath + '.backup.' + Date.now();
|
|
268
269
|
|
|
269
270
|
try {
|
|
270
|
-
|
|
271
|
+
// retrySync: transient EPERM/EBUSY from AV / Search indexer on Windows.
|
|
272
|
+
retrySync(() => fs.renameSync(projectMcpPath, backupPath));
|
|
271
273
|
results.backupPath = backupPath;
|
|
272
274
|
} catch (error) {
|
|
273
275
|
results.errors.push(`Backup failed: ${error.message}`);
|
|
@@ -315,14 +317,15 @@ function restoreFromBackup(backupPath, projectRoot = process.cwd()) {
|
|
|
315
317
|
if (fs.existsSync(projectMcpPath)) {
|
|
316
318
|
const stats = fs.lstatSync(projectMcpPath);
|
|
317
319
|
if (stats.isSymbolicLink()) {
|
|
318
|
-
fs.unlinkSync(projectMcpPath);
|
|
320
|
+
retrySync(() => fs.unlinkSync(projectMcpPath));
|
|
319
321
|
} else {
|
|
320
|
-
fs.rmSync(projectMcpPath, { recursive: true });
|
|
322
|
+
retrySync(() => fs.rmSync(projectMcpPath, { recursive: true }));
|
|
321
323
|
}
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
// Restore backup
|
|
325
|
-
|
|
326
|
+
// Restore backup — retry transient Windows locks so a legit rollback
|
|
327
|
+
// isn't lost to a momentary AV/indexer handle on the config dir.
|
|
328
|
+
retrySync(() => fs.renameSync(backupPath, projectMcpPath));
|
|
326
329
|
|
|
327
330
|
return { success: true, restored: projectMcpPath };
|
|
328
331
|
} catch (error) {
|
|
@@ -181,7 +181,10 @@ function readGlobalConfig() {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
try {
|
|
184
|
-
|
|
184
|
+
// Strip a leading BOM: a global-config.json saved by a Windows editor
|
|
185
|
+
// (Notepad) starts with U+FEFF and would make JSON.parse throw, silently
|
|
186
|
+
// dropping the user's MCP global config via the catch below.
|
|
187
|
+
const content = fs.readFileSync(configPath, 'utf8').replace(/^\uFEFF/, '');
|
|
185
188
|
return JSON.parse(content);
|
|
186
189
|
} catch (error) {
|
|
187
190
|
console.error(`Error reading global config: ${error.message}`);
|
|
@@ -13,6 +13,7 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { execSync } = require('child_process');
|
|
15
15
|
const { isWindows, getGlobalMcpDir, getLinkType } = require('./os-detector');
|
|
16
|
+
const { retrySync } = require('../synapse/utils/atomic-write');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Link status types
|
|
@@ -284,7 +285,9 @@ function createLink(projectRoot = process.cwd(), options = {}) {
|
|
|
284
285
|
const configFile = path.join(linkPath, 'global-config.json');
|
|
285
286
|
if (fs.existsSync(configFile)) {
|
|
286
287
|
const backupPath = linkPath + '.backup.' + Date.now();
|
|
287
|
-
|
|
288
|
+
// retrySync: on Windows the rename of a dir held by AV / Search
|
|
289
|
+
// indexer / a concurrent reader throws transient EPERM/EBUSY.
|
|
290
|
+
retrySync(() => fs.renameSync(linkPath, backupPath));
|
|
288
291
|
return {
|
|
289
292
|
success: false,
|
|
290
293
|
error: `Existing config backed up to ${backupPath}. Run with --migrate to merge configs.`,
|
|
@@ -293,10 +296,10 @@ function createLink(projectRoot = process.cwd(), options = {}) {
|
|
|
293
296
|
backup: backupPath,
|
|
294
297
|
};
|
|
295
298
|
}
|
|
296
|
-
fs.rmSync(linkPath, { recursive: true });
|
|
299
|
+
retrySync(() => fs.rmSync(linkPath, { recursive: true }));
|
|
297
300
|
} else {
|
|
298
301
|
// Remove symlink/junction
|
|
299
|
-
fs.unlinkSync(linkPath);
|
|
302
|
+
retrySync(() => fs.unlinkSync(linkPath));
|
|
300
303
|
}
|
|
301
304
|
} catch (error) {
|
|
302
305
|
return {
|
|
@@ -352,13 +352,14 @@ Quer que eu comece?`;
|
|
|
352
352
|
// Record phase in session state
|
|
353
353
|
await this._recordPhase(BrownfieldPhase.SYSTEM_DOCUMENTATION, context);
|
|
354
354
|
|
|
355
|
-
//
|
|
355
|
+
// Hand the discovery workflow to the executor. executeWorkflow() loads and
|
|
356
|
+
// validates the workflow definition and returns a structured handoff; the
|
|
357
|
+
// per-phase documentation is produced by the agents the workflow assigns.
|
|
358
|
+
// Progress for those agent steps is tracked via _onPhaseStart/_onPhaseComplete/
|
|
359
|
+
// _onPhaseError + handlePhaseFailureAction as the agents report in.
|
|
356
360
|
const result = await workflowExecutor.executeWorkflow(this.workflowPath, {
|
|
357
361
|
projectRoot: this.projectRoot,
|
|
358
362
|
techStack: context.techStack || {},
|
|
359
|
-
onPhaseStart: (phase) => this._onPhaseStart(phase, context),
|
|
360
|
-
onPhaseComplete: (phase, output) => this._onPhaseComplete(phase, output, context),
|
|
361
|
-
onPhaseError: (phase, error) => this._onPhaseError(phase, error, context),
|
|
362
363
|
});
|
|
363
364
|
|
|
364
365
|
// Check if workflow completed successfully
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Command — executable sibling of `sinapse route`.
|
|
3
|
+
*
|
|
4
|
+
* `route()` SHOWS the deterministic doc-first decision for a briefing (type →
|
|
5
|
+
* workflow → required docs → gate). `build()` RUNS it: it drives the
|
|
6
|
+
* BobOrchestrator decision-tree engine (Projeto Bob) from a briefing in GUIDED
|
|
7
|
+
* mode — it detects project state, routes to the greenfield / brownfield /
|
|
8
|
+
* existing-project handler, and surfaces the next action/checkpoint instead of
|
|
9
|
+
* spawning agents headlessly.
|
|
10
|
+
*
|
|
11
|
+
* Congruence guarantee: both `route()` and `build()` start from
|
|
12
|
+
* `resolveDocFirstState`, so the conversation, the doc-first gate hook, `route`
|
|
13
|
+
* and `build` can never disagree about type/workflow/gate.
|
|
14
|
+
*
|
|
15
|
+
* The heavy orchestration index (locks, observability panel, status writer) is
|
|
16
|
+
* required LAZILY — `--dry-run` never constructs it, so it stays side-effect-free.
|
|
17
|
+
*
|
|
18
|
+
* @module core/orchestration/build-command
|
|
19
|
+
* @version 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const { resolveDocFirstState } = require('./doc-first-resolver');
|
|
25
|
+
|
|
26
|
+
let chalk;
|
|
27
|
+
try {
|
|
28
|
+
chalk = require('chalk');
|
|
29
|
+
} catch {
|
|
30
|
+
const id = (s) => s;
|
|
31
|
+
chalk = {
|
|
32
|
+
green: id, red: id, yellow: Object.assign(id, { bold: id }),
|
|
33
|
+
cyan: Object.assign(id, { bold: id }), gray: id, bold: id, dim: id,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Render the compact doc-first routing header (the "plan"), shared with `route`
|
|
39
|
+
* so `build` is visibly congruent with it.
|
|
40
|
+
* @param {string} brief
|
|
41
|
+
* @param {object} state - resolveDocFirstState() result
|
|
42
|
+
*/
|
|
43
|
+
function printPlanHeader(brief, state) {
|
|
44
|
+
const ok = (b) => (b ? chalk.green('✓') : chalk.red('✗'));
|
|
45
|
+
const briefLabel = brief ? `"${brief}"` : '(no brief — using project state only)';
|
|
46
|
+
|
|
47
|
+
console.log(chalk.cyan('\n═══════════════════════════════════════════════════════════'));
|
|
48
|
+
console.log(chalk.cyan.bold(' 🛠️ SINAPSE Build — guided orchestration'));
|
|
49
|
+
console.log(chalk.cyan('═══════════════════════════════════════════════════════════\n'));
|
|
50
|
+
|
|
51
|
+
console.log(`Brief: ${chalk.gray(briefLabel)}`);
|
|
52
|
+
console.log(`Project type: ${chalk.bold(state.projectType)}`);
|
|
53
|
+
console.log(`Workflow: ${chalk.bold(state.workflow || 'SDC (no upstream re-doc)')}`);
|
|
54
|
+
console.log(
|
|
55
|
+
`Doc-first: ${state.gate.satisfied
|
|
56
|
+
? chalk.green('✅ gate satisfied')
|
|
57
|
+
: chalk.red('🚫 gate blocked — missing: ' + state.gate.missing.join(', '))}\n`,
|
|
58
|
+
);
|
|
59
|
+
void ok;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render the BobOrchestrator result in a guided, scannable way.
|
|
64
|
+
* @param {object} result - OrchestrationResult from BobOrchestrator.orchestrate
|
|
65
|
+
*/
|
|
66
|
+
function printEngineResult(result) {
|
|
67
|
+
const r = result || {};
|
|
68
|
+
console.log(chalk.bold('Engine decision:'));
|
|
69
|
+
console.log(` Project state: ${chalk.bold(r.projectState || 'unknown')}`);
|
|
70
|
+
console.log(` Action: ${chalk.bold(r.action || 'n/a')}`);
|
|
71
|
+
|
|
72
|
+
const data = r.data || {};
|
|
73
|
+
const nextStep = r.nextStep || data.nextStep;
|
|
74
|
+
const message = data.formattedMessage || data.message || data.summary;
|
|
75
|
+
if (message) {
|
|
76
|
+
console.log('\n' + chalk.gray(String(message).trim()));
|
|
77
|
+
}
|
|
78
|
+
if (nextStep) {
|
|
79
|
+
console.log(`\n${chalk.cyan('▶ Next step:')} ${chalk.bold(nextStep)}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log('');
|
|
83
|
+
if (r.success) {
|
|
84
|
+
console.log(chalk.green.bold(' ✅ Orchestration step completed.\n'));
|
|
85
|
+
} else {
|
|
86
|
+
console.log(chalk.red.bold(` 🚫 Orchestration did not complete: ${r.error || r.action || 'unknown'}\n`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Execute the `build` command.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} brief - User briefing (e.g. "criar um site pra X")
|
|
94
|
+
* @param {Object} [options]
|
|
95
|
+
* @param {string} [options.projectRoot] - Project root path
|
|
96
|
+
* @param {string} [options.type] - Explicit project_type override
|
|
97
|
+
* @param {boolean} [options.dryRun] - Show the plan + engine entry without running it
|
|
98
|
+
* @returns {Promise<{success:boolean, exitCode:number, state:object, result?:object}>}
|
|
99
|
+
*/
|
|
100
|
+
async function build(brief, options = {}) {
|
|
101
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
102
|
+
const state = resolveDocFirstState({ projectRoot, brief, projectType: options.type });
|
|
103
|
+
|
|
104
|
+
printPlanHeader(brief, state);
|
|
105
|
+
|
|
106
|
+
if (options.dryRun) {
|
|
107
|
+
console.log(chalk.yellow.bold(' (dry-run) '), 'would run BobOrchestrator.orchestrate({ userGoal })');
|
|
108
|
+
console.log(
|
|
109
|
+
chalk.gray(' No engine constructed — no locks, no panel, no side effects.\n'),
|
|
110
|
+
);
|
|
111
|
+
return { success: true, exitCode: 0, state };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Lazily require the heavy orchestration index ONLY for the real run.
|
|
115
|
+
let BobOrchestrator;
|
|
116
|
+
try {
|
|
117
|
+
({ BobOrchestrator } = require('./index'));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.log(chalk.red(` ✗ Could not load orchestration engine: ${error.message}\n`));
|
|
120
|
+
return { success: false, exitCode: 1, state };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const bob = new BobOrchestrator(projectRoot, { userGoal: brief });
|
|
124
|
+
const result = await bob.orchestrate({ userGoal: brief, storyPath: options.storyPath });
|
|
125
|
+
|
|
126
|
+
printEngineResult(result);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
success: !!(result && result.success),
|
|
130
|
+
exitCode: result && result.success === false ? 1 : 0,
|
|
131
|
+
state,
|
|
132
|
+
result,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { build };
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc-First Resolver — deterministic "what does this project need before code?"
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the Documentation-First contract (Constitution
|
|
5
|
+
* Article III). Given a project root (and optionally a user brief), it answers,
|
|
6
|
+
* with NO LLM reasoning and NO side effects:
|
|
7
|
+
*
|
|
8
|
+
* - projectType : site | lp | app | platform | saas | api | service | feature | fix
|
|
9
|
+
* - workflow : which greenfield workflow applies (reuses greenfield-handler map)
|
|
10
|
+
* - artifacts : the upstream docs required before stories (brief/prd/spec/arch)
|
|
11
|
+
* - gate : is the minimal doc-first gate satisfied? (PRD + epic + a Ready story)
|
|
12
|
+
*
|
|
13
|
+
* Two consumers share this module so they never drift:
|
|
14
|
+
* 1. `sinapse route "<brief>"` — read-only observability of the routing decision.
|
|
15
|
+
* 2. The doc-first PreToolUse gate hook — blocks code writes when `gate.satisfied`
|
|
16
|
+
* is false. The module is intentionally dependency-light (fs/path + the
|
|
17
|
+
* greenfield-handler constants only) so it is safe and fast to require inside
|
|
18
|
+
* a hook. It NEVER constructs the heavy BobOrchestrator (locks, panels, etc.).
|
|
19
|
+
*
|
|
20
|
+
* @module core/orchestration/doc-first-resolver
|
|
21
|
+
* @version 1.0.0
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
GREENFIELD_WORKFLOW_BY_TYPE,
|
|
31
|
+
resolveGreenfieldWorkflow,
|
|
32
|
+
} = require('./greenfield-handler');
|
|
33
|
+
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
// PROJECT TYPE CLASSIFICATION
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Keyword → project_type map. Mirrors the classification matrix in
|
|
40
|
+
* `.claude/rules/documentation-first.md` and `project-intelligence.md`.
|
|
41
|
+
* Order matters: more specific buckets are tested before broader ones.
|
|
42
|
+
* @constant
|
|
43
|
+
*/
|
|
44
|
+
const TYPE_KEYWORDS = Object.freeze([
|
|
45
|
+
{ type: 'fix', words: ['bug', 'conserta', 'corrige', 'corrigir', 'ajusta', 'ajustar', 'tweak', 'hotfix'] },
|
|
46
|
+
{ type: 'refactor', words: ['refatora', 'refatorar', 'refactor', 'renomeia', 'renomear', 'limpa', 'cleanup'] },
|
|
47
|
+
{ type: 'lp', words: ['landing page', 'landing', 'lp', 'sales page', 'captura', 'squeeze'] },
|
|
48
|
+
{ type: 'saas', words: ['saas', 'software as a service', 'multi-tenant', 'multitenant'] },
|
|
49
|
+
{ type: 'platform', words: ['plataforma', 'platform', 'dashboard', 'admin', 'portal', 'painel'] },
|
|
50
|
+
{ type: 'app', words: ['app mobile', 'mobile', 'ios', 'android', 'react native', 'flutter', 'aplicativo'] },
|
|
51
|
+
{ type: 'api', words: ['api', 'backend', 'microservice', 'microsserviço', 'rest', 'graphql'] },
|
|
52
|
+
{ type: 'service', words: ['worker', 'cron', 'etl', 'integration', 'integração', 'automation', 'automação'] },
|
|
53
|
+
{ type: 'site', words: ['site', 'website', 'web site', 'institucional', 'página', 'pagina'] },
|
|
54
|
+
{ type: 'feature', words: ['feature', 'funcionalidade', 'implementa', 'implementar', 'adiciona'] },
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
/** Escape a string for safe use inside a RegExp. */
|
|
58
|
+
function escapeRegExp(s) {
|
|
59
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Classify a brief string into a project_type. Returns `null` when the brief is
|
|
64
|
+
* empty or no keyword matches (caller decides the fallback). Matching is by whole
|
|
65
|
+
* word/phrase boundary (so "Asaas" does NOT match the keyword "saas").
|
|
66
|
+
* @param {string} [brief]
|
|
67
|
+
* @returns {string|null}
|
|
68
|
+
*/
|
|
69
|
+
function classifyProjectType(brief) {
|
|
70
|
+
if (!brief || typeof brief !== 'string') return null;
|
|
71
|
+
const haystack = brief.toLowerCase();
|
|
72
|
+
for (const { type, words } of TYPE_KEYWORDS) {
|
|
73
|
+
if (words.some((w) => new RegExp(`\\b${escapeRegExp(w)}\\b`, 'i').test(haystack))) {
|
|
74
|
+
return type;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
81
|
+
// REQUIRED ARTIFACTS PER PROJECT TYPE
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Upstream artifacts required BEFORE the first story, keyed by greenfield
|
|
86
|
+
* workflow. Mirrors the "Required upstream artifacts per project type" table in
|
|
87
|
+
* `documentation-first.md`. `feature` / `fix` / `refactor` do not require the
|
|
88
|
+
* upstream re-doc chain (they ride on an existing project).
|
|
89
|
+
* @constant
|
|
90
|
+
*/
|
|
91
|
+
const REQUIRED_ARTIFACTS_BY_WORKFLOW = Object.freeze({
|
|
92
|
+
'greenfield-ui': [
|
|
93
|
+
{ id: 'brief', path: 'docs/project-brief.md' },
|
|
94
|
+
{ id: 'prd', path: 'docs/prd.md' },
|
|
95
|
+
{ id: 'front-end-spec', path: 'docs/front-end-spec.md' },
|
|
96
|
+
{ id: 'architecture', path: 'docs/front-end-architecture.md' },
|
|
97
|
+
],
|
|
98
|
+
'greenfield-fullstack': [
|
|
99
|
+
{ id: 'brief', path: 'docs/project-brief.md' },
|
|
100
|
+
{ id: 'prd', path: 'docs/prd.md' },
|
|
101
|
+
{ id: 'front-end-spec', path: 'docs/front-end-spec.md' },
|
|
102
|
+
{ id: 'architecture', path: 'docs/fullstack-architecture.md' },
|
|
103
|
+
],
|
|
104
|
+
'greenfield-service': [
|
|
105
|
+
{ id: 'brief', path: 'docs/project-brief.md' },
|
|
106
|
+
{ id: 'prd', path: 'docs/prd.md' },
|
|
107
|
+
{ id: 'architecture', path: 'docs/service-architecture.md' },
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/** Project types that bypass the upstream greenfield doc-chain. */
|
|
112
|
+
const LIGHT_TYPES = Object.freeze(['feature', 'fix', 'refactor']);
|
|
113
|
+
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
115
|
+
// PROJECT MATURITY
|
|
116
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
117
|
+
|
|
118
|
+
/** Directories whose presence (with real files) signals existing application code. */
|
|
119
|
+
const CODE_DIRS = Object.freeze(['src', 'app', 'packages', 'components', 'pages', 'lib']);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Assess project maturity for gate scoping. Two signals matter:
|
|
123
|
+
*
|
|
124
|
+
* - isFrameworkRepo : this IS the SINAPSE framework's own repo (package.json
|
|
125
|
+
* name === 'sinapse-ai'). Framework development operates
|
|
126
|
+
* above the story layer (Constitution Art. III exception),
|
|
127
|
+
* so the doc-first gate must never block it.
|
|
128
|
+
* - isGreenfield : the project has no substantial application code yet — a
|
|
129
|
+
* brand-new project. The hard doc-first gate applies HERE
|
|
130
|
+
* (this is the "criar um site → straight to code" case).
|
|
131
|
+
* Existing projects defer to the lighter story-gate.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} projectRoot
|
|
134
|
+
* @returns {{ isFrameworkRepo: boolean, isGreenfield: boolean }}
|
|
135
|
+
*/
|
|
136
|
+
function assessProjectMaturity(projectRoot) {
|
|
137
|
+
let isFrameworkRepo = false;
|
|
138
|
+
try {
|
|
139
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
140
|
+
if (pkg && pkg.name === 'sinapse-ai') isFrameworkRepo = true;
|
|
141
|
+
} catch {
|
|
142
|
+
// no package.json — fine
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Greenfield = no substantial application code yet. A lone package.json does
|
|
146
|
+
// NOT flip this off — otherwise scaffolding package.json first (an exempt
|
|
147
|
+
// file) would bypass the gate. Only populated code dirs count as "existing".
|
|
148
|
+
const hasCode = CODE_DIRS.some((d) => dirHasAnyFile(path.join(projectRoot, d)));
|
|
149
|
+
const isGreenfield = !hasCode;
|
|
150
|
+
|
|
151
|
+
return { isFrameworkRepo, isGreenfield };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
155
|
+
// MINIMAL DOC-FIRST GATE
|
|
156
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
157
|
+
|
|
158
|
+
const VALID_STORY_STATUSES = Object.freeze(['ready', 'inprogress', 'inreview', 'done']);
|
|
159
|
+
|
|
160
|
+
/** True if a `docs/prd.md` (or sharded `docs/prd/`) exists and is non-trivial. */
|
|
161
|
+
function hasPrd(projectRoot) {
|
|
162
|
+
const file = path.join(projectRoot, 'docs', 'prd.md');
|
|
163
|
+
if (fileHasContent(file)) return true;
|
|
164
|
+
return dirHasMarkdown(path.join(projectRoot, 'docs', 'prd'));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** True if at least one epic exists under `docs/epics/` or `docs/stories/epics/`. */
|
|
168
|
+
function hasEpic(projectRoot) {
|
|
169
|
+
return (
|
|
170
|
+
dirHasMarkdown(path.join(projectRoot, 'docs', 'epics')) ||
|
|
171
|
+
dirHasMarkdown(path.join(projectRoot, 'docs', 'stories', 'epics'))
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** True if at least one story under `docs/stories/` is at a >= Ready status. */
|
|
176
|
+
function hasReadyStory(projectRoot) {
|
|
177
|
+
const dir = path.join(projectRoot, 'docs', 'stories');
|
|
178
|
+
return walkMarkdown(dir).some((f) => storyStatusIsReady(f));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Parse a story file's frontmatter `status:` and decide whether it is >= Ready.
|
|
183
|
+
* Uses a bounded, non-greedy regex (the greedy `[\w\s]*` form historically
|
|
184
|
+
* captured the next line — see the story-gate memory).
|
|
185
|
+
* @param {string} file
|
|
186
|
+
* @returns {boolean}
|
|
187
|
+
*/
|
|
188
|
+
function storyStatusIsReady(file) {
|
|
189
|
+
let content;
|
|
190
|
+
try {
|
|
191
|
+
content = fs.readFileSync(file, 'utf8');
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const m = content.match(/status:\s*["']?([a-z][a-z ]*?)["']?\s*(?:\r?\n|$)/i);
|
|
196
|
+
if (!m) return false;
|
|
197
|
+
const status = m[1].toLowerCase().replace(/\s+/g, '');
|
|
198
|
+
return VALID_STORY_STATUSES.includes(status);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
202
|
+
// MAIN RESOLVER
|
|
203
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Resolve the full doc-first picture for a project.
|
|
207
|
+
*
|
|
208
|
+
* @param {Object} [opts]
|
|
209
|
+
* @param {string} [opts.projectRoot=process.cwd()] - Project root.
|
|
210
|
+
* @param {string} [opts.brief] - User briefing (used to classify project type).
|
|
211
|
+
* @param {string} [opts.projectType] - Explicit type override (skips classification).
|
|
212
|
+
* @returns {{
|
|
213
|
+
* brief: string|null,
|
|
214
|
+
* projectType: string,
|
|
215
|
+
* classified: boolean,
|
|
216
|
+
* isLightType: boolean,
|
|
217
|
+
* workflow: string|null,
|
|
218
|
+
* artifacts: Array<{id:string, path:string, exists:boolean}>,
|
|
219
|
+
* gate: { prd:boolean, epic:boolean, readyStory:boolean, satisfied:boolean, missing:string[] }
|
|
220
|
+
* }}
|
|
221
|
+
*/
|
|
222
|
+
function resolveDocFirstState(opts = {}) {
|
|
223
|
+
const projectRoot = opts.projectRoot || process.cwd();
|
|
224
|
+
const brief = opts.brief || null;
|
|
225
|
+
const classifiedType = opts.projectType || classifyProjectType(brief);
|
|
226
|
+
const projectType = classifiedType || 'site'; // safe default: assume a full project needs docs
|
|
227
|
+
const isLightType = LIGHT_TYPES.includes(projectType);
|
|
228
|
+
|
|
229
|
+
const workflow = isLightType ? null : resolveGreenfieldWorkflow(projectType);
|
|
230
|
+
|
|
231
|
+
const artifacts = (REQUIRED_ARTIFACTS_BY_WORKFLOW[workflow] || []).map((a) => ({
|
|
232
|
+
id: a.id,
|
|
233
|
+
path: a.path,
|
|
234
|
+
exists: fileHasContent(path.join(projectRoot, a.path)),
|
|
235
|
+
}));
|
|
236
|
+
|
|
237
|
+
const prd = hasPrd(projectRoot);
|
|
238
|
+
const epic = hasEpic(projectRoot);
|
|
239
|
+
const readyStory = hasReadyStory(projectRoot);
|
|
240
|
+
|
|
241
|
+
// Light types ride on an existing project: the minimal gate is "a Ready story"
|
|
242
|
+
// (the existing story-gate). Full projects additionally need PRD + epic.
|
|
243
|
+
const missing = [];
|
|
244
|
+
if (!readyStory) missing.push('story (status >= Ready)');
|
|
245
|
+
if (!isLightType) {
|
|
246
|
+
if (!prd) missing.push('PRD (docs/prd.md)');
|
|
247
|
+
if (!epic) missing.push('epic (docs/epics/)');
|
|
248
|
+
}
|
|
249
|
+
const satisfied = missing.length === 0;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
brief,
|
|
253
|
+
projectType,
|
|
254
|
+
classified: Boolean(classifiedType),
|
|
255
|
+
isLightType,
|
|
256
|
+
workflow,
|
|
257
|
+
artifacts,
|
|
258
|
+
gate: { prd, epic, readyStory, satisfied, missing },
|
|
259
|
+
maturity: assessProjectMaturity(projectRoot),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
264
|
+
// FS HELPERS (lean, no deps)
|
|
265
|
+
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
266
|
+
|
|
267
|
+
function fileHasContent(file) {
|
|
268
|
+
try {
|
|
269
|
+
const stat = fs.statSync(file);
|
|
270
|
+
return stat.isFile() && stat.size > 0;
|
|
271
|
+
} catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function dirHasMarkdown(dir) {
|
|
277
|
+
try {
|
|
278
|
+
return fs.readdirSync(dir).some((f) => f.toLowerCase().endsWith('.md'));
|
|
279
|
+
} catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** True if `dir` exists and contains at least one regular file (any depth-1 entry). */
|
|
285
|
+
function dirHasAnyFile(dir) {
|
|
286
|
+
try {
|
|
287
|
+
return fs.readdirSync(dir, { withFileTypes: true }).some((e) => e.isFile() || e.isDirectory());
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Recursively collect `.md` files under a dir (bounded depth, fail-safe). */
|
|
294
|
+
function walkMarkdown(dir, depth = 0) {
|
|
295
|
+
if (depth > 4) return [];
|
|
296
|
+
let entries;
|
|
297
|
+
try {
|
|
298
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
299
|
+
} catch {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
const out = [];
|
|
303
|
+
for (const e of entries) {
|
|
304
|
+
const full = path.join(dir, e.name);
|
|
305
|
+
if (e.isDirectory()) {
|
|
306
|
+
out.push(...walkMarkdown(full, depth + 1));
|
|
307
|
+
} else if (e.name.toLowerCase().endsWith('.md')) {
|
|
308
|
+
out.push(full);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
classifyProjectType,
|
|
316
|
+
resolveDocFirstState,
|
|
317
|
+
assessProjectMaturity,
|
|
318
|
+
REQUIRED_ARTIFACTS_BY_WORKFLOW,
|
|
319
|
+
TYPE_KEYWORDS,
|
|
320
|
+
// re-exported for callers that want the raw map
|
|
321
|
+
GREENFIELD_WORKFLOW_BY_TYPE,
|
|
322
|
+
};
|
|
@@ -115,6 +115,9 @@ class Epic4Executor extends EpicExecutor {
|
|
|
115
115
|
return this._completeExecution({
|
|
116
116
|
implementationPath: planPath,
|
|
117
117
|
planPath,
|
|
118
|
+
// F5 (epic: orchestration-consolidation): propagate the real plan object
|
|
119
|
+
// so the downstream epic4_to_epic6 gate can verify it is not degraded/stub.
|
|
120
|
+
plan: buildResult.plan,
|
|
118
121
|
build: buildResult,
|
|
119
122
|
reportPath: buildResult.reportPath,
|
|
120
123
|
phases: buildResult.phases,
|
|
@@ -54,7 +54,9 @@ const DEFAULT_GATE_CONFIG = {
|
|
|
54
54
|
blocking: true,
|
|
55
55
|
requireTests: true,
|
|
56
56
|
minTestCoverage: 0,
|
|
57
|
-
|
|
57
|
+
// F5 (epic: orchestration-consolidation): `plan_is_real` gives this gate teeth —
|
|
58
|
+
// a degraded/stub/absent/subtask-less plan is BLOCKED, not waved through.
|
|
59
|
+
checks: ['plan_complete', 'plan_is_real', 'implementation_exists', 'no_critical_errors'],
|
|
58
60
|
},
|
|
59
61
|
};
|
|
60
62
|
|
|
@@ -148,11 +150,7 @@ class GateEvaluator {
|
|
|
148
150
|
const checks = await this._runGateChecks(fromEpic, toEpic, epicResult, gateConfig);
|
|
149
151
|
result.checks = checks;
|
|
150
152
|
|
|
151
|
-
//
|
|
152
|
-
const passedChecks = checks.filter((c) => c.passed).length;
|
|
153
|
-
result.score = checks.length > 0 ? (passedChecks / checks.length) * 5 : 5;
|
|
154
|
-
|
|
155
|
-
// Collect issues
|
|
153
|
+
// Collect issues from failed checks
|
|
156
154
|
result.issues = checks
|
|
157
155
|
.filter((c) => !c.passed)
|
|
158
156
|
.map((c) => ({
|
|
@@ -161,6 +159,22 @@ class GateEvaluator {
|
|
|
161
159
|
severity: c.severity || 'medium',
|
|
162
160
|
}));
|
|
163
161
|
|
|
162
|
+
// Calculate score — honesty invariant (F5, epic: orchestration-consolidation):
|
|
163
|
+
// a gate that evaluated ZERO checks verified NOTHING. It must NOT receive the
|
|
164
|
+
// max score (the old `: 5` was theater — an unconfigured gate always passed).
|
|
165
|
+
// Score 0 + an explicit issue so the verdict reflects "no real verification".
|
|
166
|
+
if (checks.length === 0) {
|
|
167
|
+
result.score = 0;
|
|
168
|
+
result.issues.push({
|
|
169
|
+
check: 'no_checks_evaluated',
|
|
170
|
+
message: 'Gate evaluated zero checks — nothing was actually verified',
|
|
171
|
+
severity: this.strictMode ? 'critical' : 'high',
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
const passedChecks = checks.filter((c) => c.passed).length;
|
|
175
|
+
result.score = (passedChecks / checks.length) * 5;
|
|
176
|
+
}
|
|
177
|
+
|
|
164
178
|
// Determine verdict (AC2)
|
|
165
179
|
result.verdict = this._determineVerdict(result, gateConfig);
|
|
166
180
|
|
|
@@ -279,11 +293,46 @@ class GateEvaluator {
|
|
|
279
293
|
break;
|
|
280
294
|
|
|
281
295
|
// Epic 4 checks
|
|
282
|
-
case 'plan_complete':
|
|
283
|
-
|
|
296
|
+
case 'plan_complete': {
|
|
297
|
+
// F5: hardened but backward-compatible. When a real plan OBJECT is present
|
|
298
|
+
// (wired in via epic-4-executor), require real phases. When only a path/flag
|
|
299
|
+
// is present (legacy callers), keep the old signal so existing behavior holds.
|
|
300
|
+
const plan = epicResult.plan;
|
|
301
|
+
if (plan && typeof plan === 'object') {
|
|
302
|
+
result.passed = Array.isArray(plan.phases) && plan.phases.length > 0;
|
|
303
|
+
} else {
|
|
304
|
+
result.passed = !!epicResult.planPath || epicResult.planComplete === true;
|
|
305
|
+
}
|
|
284
306
|
result.message = result.passed ? 'Implementation plan complete' : 'Plan not complete';
|
|
285
307
|
result.severity = 'high';
|
|
286
308
|
break;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
case 'plan_is_real': {
|
|
312
|
+
// F5 (epic: orchestration-consolidation) — the gate's teeth. Honesty
|
|
313
|
+
// invariant: a degraded/stub/absent/subtask-less plan is NOT a real artifact
|
|
314
|
+
// and MUST block, not slip through. Critical severity → verdict BLOCKED.
|
|
315
|
+
const plan = epicResult.plan;
|
|
316
|
+
if (!plan || typeof plan !== 'object') {
|
|
317
|
+
result.passed = false;
|
|
318
|
+
result.message = 'No real plan object to inspect (plan absent)';
|
|
319
|
+
} else if (plan.degraded === true || plan.stub === true) {
|
|
320
|
+
result.passed = false;
|
|
321
|
+
result.message = `Plan is degraded/stub${plan.reason ? ` (${plan.reason})` : ''}`;
|
|
322
|
+
} else {
|
|
323
|
+
const phases = Array.isArray(plan.phases) ? plan.phases : [];
|
|
324
|
+
const subtaskCount = phases.reduce(
|
|
325
|
+
(n, p) => n + (Array.isArray(p.subtasks) ? p.subtasks.length : 0),
|
|
326
|
+
0,
|
|
327
|
+
);
|
|
328
|
+
result.passed = phases.length > 0 && subtaskCount > 0;
|
|
329
|
+
result.message = result.passed
|
|
330
|
+
? `Plan is real (${phases.length} phase(s), ${subtaskCount} subtask(s))`
|
|
331
|
+
: 'Plan has no subtasks';
|
|
332
|
+
}
|
|
333
|
+
result.severity = 'critical';
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
287
336
|
|
|
288
337
|
case 'implementation_exists':
|
|
289
338
|
result.passed = !!epicResult.implementationPath || epicResult.codeChanges?.length > 0;
|
|
@@ -342,7 +391,7 @@ class GateEvaluator {
|
|
|
342
391
|
case 3:
|
|
343
392
|
return ['spec_exists', 'complexity_assessed'];
|
|
344
393
|
case 4:
|
|
345
|
-
return ['plan_complete', 'no_critical_errors'];
|
|
394
|
+
return ['plan_complete', 'plan_is_real', 'no_critical_errors'];
|
|
346
395
|
case 6:
|
|
347
396
|
return ['qa_report_exists', 'verdict_generated'];
|
|
348
397
|
default:
|