@polymorphism-tech/morph-spec 9.0.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
package/src/lib/risk-detector.js
CHANGED
|
@@ -1,81 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Risk Detector — decides whether a gate should pause execution.
|
|
3
|
-
*
|
|
4
|
-
* In `trust: "auto"` mode (default), gates auto-pass UNLESS one of these
|
|
5
|
-
* risk signals triggers a forced pause. In `trust: "manual"` mode, gates
|
|
6
|
-
* always pause via AskUserQuestion.
|
|
7
|
-
*
|
|
8
|
-
* Signals checked (in order):
|
|
9
|
-
* - designSystemDivergence: boolean — conflicting design sources detected
|
|
10
|
-
* - standardsViolations: string[] — violations of the plan's forbidden patterns
|
|
11
|
-
* - verificationStatus: string — último `morph-spec verify` ('fail' pausa, qualquer gate)
|
|
12
|
-
* - e2eStatus + e2eReason + gate — nó e2e do último verify. Um `skip`
|
|
13
|
-
* cuja razão NÃO seja dispensa legítima pausa SÓ no ship (gate 'review')
|
|
14
|
-
* - verificationStale + gate: bool/str — stamp de um commit cujo código já mudou
|
|
15
|
-
* (fora de .morph/) pausa SÓ no ship (gate 'review')
|
|
16
|
-
* - reviewReports + gate: object — `reviewSignal` (review-reports.js): os
|
|
17
|
-
* relatórios exigidos e a cobertura dos Cn. No ship, só SHIP sem bloqueador
|
|
18
|
-
* em todo exigido, com todo Cn PROVADO, libera
|
|
19
|
-
* - mediaClaims + gate: object — { section, featureGeneratesMedia }
|
|
20
|
-
* - screenChecklist + gate: object — { mockups, legacy, screens, problems, exempt?,
|
|
21
|
-
* section?, uncovered? } derivado do 1-design/mockups.md. No uiux, checklist
|
|
22
|
-
* de tela incompleto pausa; no ship, item decidido sem a conferência do
|
|
23
|
-
* verificador pausa. Mockup legado é isento nos dois, com a razão listada
|
|
24
|
-
* - planChecks + gate: string — `readPlanChecks(...).status`: os checks
|
|
25
|
-
* extraídos contra o plano; no gate do plano, tudo que não é `fresh` pausa
|
|
26
|
-
* - sliceCount: number — fatias extraídas (pausa acima de MAX_SLICES)
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
1
|
import { MAX_SLICES } from './plan/checks.js';
|
|
30
2
|
import { BLOCKER_TYPES, reviewFileName } from './review-reports.js';
|
|
31
3
|
|
|
32
|
-
// Caminho alterado que pede a revisão de segurança (persona `security`, opus).
|
|
33
4
|
const SECURITY_PATH_RE = /(^|[/._-])(auth|login|senha|password|token|secret|crypto|permiss|rbac|acl|lgpd|pii|privacy|payment|pagamento|billing|cobranca|invoice|asaas|stripe|migrations?)([/._-]|$)/i;
|
|
34
5
|
|
|
35
|
-
//
|
|
36
|
-
// of trust mode — a type policy, not a per-feature risk signal. Mirrors
|
|
37
|
-
// `forcedPauseGates` in src/core/work-types.js (hotfix.review); kept here too
|
|
38
|
-
// because risk-detector is the deterministic layer consumed independent of
|
|
39
|
-
// the work-type config object.
|
|
6
|
+
// Espelha `forcedPauseGates` (hotfix.review) de src/core/work-types.js: os dois mudam juntos.
|
|
40
7
|
const FORCED_PAUSE_WORK_TYPES = ['hotfix'];
|
|
41
8
|
|
|
42
|
-
//
|
|
43
|
-
// nó e2e pula. Casadas por PADRÃO e não por igualdade porque duas delas
|
|
44
|
-
// carregam dado variável (o nome do grupo da task). Ordem irrelevante: os
|
|
45
|
-
// padrões são disjuntos.
|
|
9
|
+
// Razões literais que `e2e-check.js` emite ao pular; casadas por padrão porque algumas carregam dado variável.
|
|
46
10
|
const E2E_SKIP_PATTERNS = [
|
|
47
|
-
// "docker is not available in this environment — e2e could not run"
|
|
48
|
-
// (e2e-check.js). A app não foi exercida porque a MÁQUINA não podia.
|
|
49
11
|
[/docker is not available/i, 'environment'],
|
|
50
|
-
// `--skip-e2e` — o operador pediu para pular.
|
|
51
12
|
[/^requested$/i, 'requested'],
|
|
52
|
-
// "project does not containerize (no compose file)" — não há app para subir.
|
|
53
13
|
[/does not containerize/i, 'project'],
|
|
14
|
+
[/^not applicable:/i, 'not-applicable'],
|
|
54
15
|
];
|
|
55
16
|
|
|
56
|
-
//
|
|
57
|
-
// diz que ele não precisava rodar. `unknown` está aqui de propósito — inclui a
|
|
58
|
-
// razão vazia/nula, e uma dispensa sem motivo é o mesmo no-op silencioso que
|
|
59
|
-
// esta regra existe para eliminar.
|
|
17
|
+
// `unknown` (razão vazia) pausa de propósito: dispensa sem motivo é o no-op silencioso que a regra elimina.
|
|
60
18
|
const E2E_SKIP_CLASSES_THAT_PAUSE = ['environment', 'requested', 'unknown'];
|
|
61
19
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* prova. Existe porque `verificationStatus` é o rollup do `verify` e um nó que
|
|
65
|
-
* pula NÃO o reprova: sem classificar a razão, "o e2e rodou e passou" e "o e2e
|
|
66
|
-
* nunca rodou" chegam ao gate como o mesmo `pass`.
|
|
67
|
-
*
|
|
68
|
-
* A assimetria é o ponto: `project` descreve o REPOSITÓRIO dizendo que
|
|
69
|
-
* não há runtime a exercer (dispensa legítima, registrada com motivo);
|
|
70
|
-
* `environment`/`requested` descrevem a MÁQUINA ou o operador, e nenhum dos
|
|
71
|
-
* dois é evidência sobre o código. `unknown` cai no lado seguro pelo mesmo
|
|
72
|
-
* motivo: recusar é barato, aprovar sem prova não é.
|
|
73
|
-
*
|
|
74
|
-
* @param {string|null|undefined} reason - `verification.e2eReason` (a razão
|
|
75
|
-
* literal que o `morph-spec verify` carimbou). Vazia/ausente ⇒ 'unknown' —
|
|
76
|
-
* DESCONHECIDO, jamais lido como dispensa.
|
|
77
|
-
* @returns {'environment'|'requested'|'project'|'unknown'}
|
|
78
|
-
*/
|
|
20
|
+
// `project` diz que o repositório não tem runtime (dispensa legítima); `environment`/`requested` falam
|
|
21
|
+
// da máquina ou do operador, nunca do código. Razão vazia é `unknown`, jamais dispensa.
|
|
79
22
|
export function classifyE2eSkip(reason) {
|
|
80
23
|
const text = typeof reason === 'string' ? reason.trim() : '';
|
|
81
24
|
if (text === '') return 'unknown';
|
|
@@ -85,7 +28,6 @@ export function classifyE2eSkip(reason) {
|
|
|
85
28
|
return 'unknown';
|
|
86
29
|
}
|
|
87
30
|
|
|
88
|
-
/** Mensagem de pausa por classe — nomeia SEMPRE a razão literal do verify. */
|
|
89
31
|
function e2ePauseReason(klass, reason) {
|
|
90
32
|
const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : null;
|
|
91
33
|
if (klass === 'environment') {
|
|
@@ -102,38 +44,32 @@ function e2ePauseReason(klass, reason) {
|
|
|
102
44
|
return `e2e pulado por razão não classificada: "${literal}" — sem classificação, não há dispensa`;
|
|
103
45
|
}
|
|
104
46
|
|
|
105
|
-
/** A razão gravada no sinal quando o checklist de tela isenta um mockup legado. */
|
|
106
47
|
export const LEGACY_MOCKUP_EXEMPTION = 'checklist dispensado: mockup no formato antigo';
|
|
107
48
|
|
|
108
|
-
/** Os dois primeiros itens de uma lista, e quantos ficaram de fora. */
|
|
109
49
|
function firstOf(items) {
|
|
110
50
|
const shown = items.slice(0, 2).join('; ');
|
|
111
51
|
return items.length > 2 ? `${shown} (+${items.length - 2})` : shown;
|
|
112
52
|
}
|
|
113
53
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* aprovação automática que passasse onde o gate-check pausa seria o atalho.
|
|
118
|
-
*
|
|
119
|
-
* Mockup ausente (`mockups: false`) não conta — nem toda feature de UI tem
|
|
120
|
-
* tela —, e o legado chega sem problemas: a auditoria o isenta.
|
|
121
|
-
*
|
|
122
|
-
* @param {object|undefined} screenChecklist - o sinal de `collectScreenChecklist`
|
|
123
|
-
* @returns {string|null}
|
|
124
|
-
*/
|
|
54
|
+
// Exportada porque approve/advance `auto` recusam pela mesma regra. Sem design de tela não conta,
|
|
55
|
+
// e o legado chega sem problemas: a auditoria o isenta. Checklist
|
|
56
|
+
// completo ainda deve o protótipo e os prints 1280/390 (`prototypes`).
|
|
125
57
|
export function uiuxChecklistPause(screenChecklist) {
|
|
126
58
|
if (screenChecklist?.mockups !== true) return null;
|
|
127
59
|
const problems = screenChecklist.problems ?? [];
|
|
128
|
-
if (problems.length
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
60
|
+
if (problems.length > 0) {
|
|
61
|
+
return `checklist de tela incompleto em 1-design/mockups.md: ${firstOf(problems)}`
|
|
62
|
+
+ ' — volume, paginação, ordenação, filtro, os três estados, mobile e abas por tela'
|
|
63
|
+
+ ' ("não se aplica" vale, se escrito)';
|
|
64
|
+
}
|
|
65
|
+
const prototypes = screenChecklist.prototypes ?? [];
|
|
66
|
+
if (prototypes.length === 0) return null;
|
|
67
|
+
return `protótipo incompleto: ${firstOf(prototypes)}`
|
|
68
|
+
+ ' — cada tela aponta o HTML renderizado e deixa os prints de 1280 e 390 ao lado dele';
|
|
132
69
|
}
|
|
133
70
|
|
|
134
71
|
const ROLE_PERSONA = { verifier: 'morph-verifier', reviewer: 'morph-reviewer', security: 'morph-security' };
|
|
135
72
|
|
|
136
|
-
/** Por que os relatórios de review seguram o ship, ou null. */
|
|
137
73
|
export function reviewPause(signal) {
|
|
138
74
|
const required = signal?.required ?? ['verifier'];
|
|
139
75
|
const reports = signal?.reports ?? {};
|
|
@@ -164,6 +100,7 @@ export function reviewPause(signal) {
|
|
|
164
100
|
if (r.verdict !== 'SHIP') return `veredito do ${role} é ${r.verdict}`;
|
|
165
101
|
}
|
|
166
102
|
const checks = signal?.checks;
|
|
103
|
+
if (checks?.status === 'not-required') return null;
|
|
167
104
|
if (!checks || checks.status !== 'ok') {
|
|
168
105
|
return 'sem `2-plan/checks.json` legível — a cobertura dos cenários pelo verificador não é conferível';
|
|
169
106
|
}
|
|
@@ -190,14 +127,7 @@ function reviewSummary(signal) {
|
|
|
190
127
|
return `review: ${verdicts}; ${blockers} bloqueador(es)${checks}`;
|
|
191
128
|
}
|
|
192
129
|
|
|
193
|
-
|
|
194
|
-
* Modelo do verificador: `opus` quando a feature mexe em prompt/modelo ou
|
|
195
|
-
* gera mídia; `sonnet` no resto. Caminho sensível não muda o verificador:
|
|
196
|
-
* despacha a persona `security` ({@link securityReviewFor}).
|
|
197
|
-
*
|
|
198
|
-
* @param {{ promptModelTouched?: boolean, generatesMedia?: boolean }} input
|
|
199
|
-
* @returns {{ model: 'opus'|'sonnet', reasons: string[] }}
|
|
200
|
-
*/
|
|
130
|
+
// Caminho sensível não muda o verificador: despacha a persona `security` (`securityReviewFor`).
|
|
201
131
|
export function verifierModelFor({ promptModelTouched = false, generatesMedia = false } = {}) {
|
|
202
132
|
const reasons = [];
|
|
203
133
|
if (promptModelTouched) reasons.push('prompt/modelo alterado');
|
|
@@ -205,13 +135,6 @@ export function verifierModelFor({ promptModelTouched = false, generatesMedia =
|
|
|
205
135
|
return { model: reasons.length ? 'opus' : 'sonnet', reasons };
|
|
206
136
|
}
|
|
207
137
|
|
|
208
|
-
/**
|
|
209
|
-
* A revisão de segurança é exigida quando o diff toca auth, segredo,
|
|
210
|
-
* permissão, dado pessoal, pagamento ou migration.
|
|
211
|
-
*
|
|
212
|
-
* @param {string[]} changedFiles
|
|
213
|
-
* @returns {{ required: boolean, reasons: string[] }}
|
|
214
|
-
*/
|
|
215
138
|
export function securityReviewFor(changedFiles = []) {
|
|
216
139
|
const risky = changedFiles.filter((f) => SECURITY_PATH_RE.test(f.replaceAll('\\', '/')));
|
|
217
140
|
return { required: risky.length > 0, reasons: risky.length ? [`caminho sensível: ${firstOf(risky)}`] : [] };
|
|
@@ -224,27 +147,13 @@ const PLAN_CHECKS_REASONS = {
|
|
|
224
147
|
stale: '2-plan/checks.json não corresponde ao plan.md atual',
|
|
225
148
|
};
|
|
226
149
|
|
|
227
|
-
|
|
228
|
-
* Por que o gate do plano não fecha sem pessoa: os checks extraídos são o que
|
|
229
|
-
* o apply despacha, o report-check confere e a review cobra — um plano sem
|
|
230
|
-
* eles, ou com os de outra versão do plano, aprovaria critérios que ninguém
|
|
231
|
-
* extraiu.
|
|
232
|
-
* Regra única do gate-check (pausa) e do approve/advance automáticos (recusa).
|
|
233
|
-
*
|
|
234
|
-
* @param {string|undefined} status - `readPlanChecks(...).status`
|
|
235
|
-
* @returns {string|null}
|
|
236
|
-
*/
|
|
150
|
+
// Regra única do gate-check (pausa) e do approve/advance automáticos (recusa).
|
|
237
151
|
export function planChecksPause(status) {
|
|
238
152
|
if (status === undefined || status === 'fresh') return null;
|
|
239
153
|
const what = PLAN_CHECKS_REASONS[status] ?? `checks.json em estado "${status}"`;
|
|
240
154
|
return `${what} — rode \`morph-spec plan-check {feature}\` até zero erro antes do gate do plano`;
|
|
241
155
|
}
|
|
242
156
|
|
|
243
|
-
/**
|
|
244
|
-
* @param {object} config - .morph/config/config.json contents (uses `trust`)
|
|
245
|
-
* @param {object} signals
|
|
246
|
-
* @returns {{ pause: boolean, reason: string }}
|
|
247
|
-
*/
|
|
248
157
|
export function shouldPauseGate(config, signals) {
|
|
249
158
|
const trust = config?.trust ?? 'auto';
|
|
250
159
|
|
|
@@ -270,10 +179,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
270
179
|
};
|
|
271
180
|
}
|
|
272
181
|
|
|
273
|
-
//
|
|
274
|
-
// testes/validadores) está falhando — código antes do juiz vale também
|
|
275
|
-
// para a decisão de pausa. Só 'fail' pausa: ausência de stamp não é sinal
|
|
276
|
-
// (features antigas não têm o campo).
|
|
182
|
+
// Só 'fail' pausa aqui: features antigas não têm o stamp.
|
|
277
183
|
if (signals.verificationStatus === 'fail') {
|
|
278
184
|
return {
|
|
279
185
|
pause: true,
|
|
@@ -281,7 +187,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
281
187
|
};
|
|
282
188
|
}
|
|
283
189
|
|
|
284
|
-
// No ship, ausência de stamp não é
|
|
190
|
+
// No ship, ausência de stamp não é verde.
|
|
285
191
|
if (signals.gate === 'review' && !signals.verificationStatus) {
|
|
286
192
|
return {
|
|
287
193
|
pause: true,
|
|
@@ -289,16 +195,8 @@ export function shouldPauseGate(config, signals) {
|
|
|
289
195
|
};
|
|
290
196
|
}
|
|
291
197
|
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
// cego — auto-passando um Gate 3 sobre uma app que ninguém subiu. A razão
|
|
295
|
-
// literal decide: dispensa legítima (o projeto não containeriza) passa e
|
|
296
|
-
// fica REGISTRADA nos signals; ausência de
|
|
297
|
-
// prova (docker indisponível, `--skip-e2e`, razão nenhuma) pausa. É a prosa
|
|
298
|
-
// do morph-review virando regra — "docker indisponível não é dispensa" — e
|
|
299
|
-
// é o que dispensa o `gate-decision --pause false` manual que ela mandava
|
|
300
|
-
// rodar. Só no review: pausar o Gate 1/2 numa máquina sem docker travaria
|
|
301
|
-
// trabalho legítimo de planejamento.
|
|
198
|
+
// Skip não reprova o rollup, então só a razão literal separa dispensa de ausência de prova.
|
|
199
|
+
// Só no ship: pausar plan/uiux numa máquina sem docker travaria planejamento legítimo.
|
|
302
200
|
if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
|
|
303
201
|
const klass = classifyE2eSkip(signals.e2eReason);
|
|
304
202
|
if (E2E_SKIP_CLASSES_THAT_PAUSE.includes(klass)) {
|
|
@@ -306,13 +204,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
306
204
|
}
|
|
307
205
|
}
|
|
308
206
|
|
|
309
|
-
//
|
|
310
|
-
// sha diverge do HEAD atual significa que código mudou depois do último
|
|
311
|
-
// `morph-spec verify` — o resultado carimbado não cobre mais o que está
|
|
312
|
-
// prestes a ser aprovado. Pausa só aqui, nunca em plan/uiux: essa
|
|
313
|
-
// é uma regra de ship, não uma regra geral de gate. Stamp AUSENTE não entra
|
|
314
|
-
// aqui (verificationStale é fail-open para `false` em collectSignals
|
|
315
|
-
// quando não há stamp) — features legadas sem stamp continuam liberadas.
|
|
207
|
+
// Regra de ship, não de gate em geral; stamp ausente é fail-open (`false`) em collectSignals.
|
|
316
208
|
if (signals.gate === 'review' && signals.verificationStale === true) {
|
|
317
209
|
return {
|
|
318
210
|
pause: true,
|
|
@@ -325,18 +217,9 @@ export function shouldPauseGate(config, signals) {
|
|
|
325
217
|
if (reason) return { pause: true, reason };
|
|
326
218
|
}
|
|
327
219
|
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
// unitário: os dois continuam verdes com o prompt trocado.
|
|
332
|
-
//
|
|
333
|
-
// DEPOIS dos relatórios de review, de propósito: relatório ausente é o
|
|
334
|
-
// defeito mais grave dos dois e é a razão reportada.
|
|
335
|
-
//
|
|
336
|
-
// PAUSA, jamais bloqueia: `approve review` não ganha precondição nova. Uma
|
|
337
|
-
// segunda precondição dura ali tornaria inaprovável, inclusive por decisão
|
|
338
|
-
// humana, toda feature legada. Pausa é a severidade certa — o humano lê a
|
|
339
|
-
// razão e decide.
|
|
220
|
+
// Prompt e modelo não são lidos por build nem por teste. Depois dos relatórios de review de propósito:
|
|
221
|
+
// relatório ausente é o defeito mais grave. Pausa, nunca bloqueia: bloquear tornaria inaprovável
|
|
222
|
+
// toda feature legada.
|
|
340
223
|
if (signals.gate === 'review' && (signals.promptModel?.touched?.length ?? 0) > 0) {
|
|
341
224
|
const touched = signals.promptModel.touched;
|
|
342
225
|
const where = `${touched.slice(0, 3).join(', ')}${touched.length > 3 ? ` (+${touched.length - 3})` : ''}`;
|
|
@@ -358,9 +241,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
358
241
|
+ ' — rode `morph-spec verify {feature}` (escopo de feature) antes do ship',
|
|
359
242
|
};
|
|
360
243
|
}
|
|
361
|
-
// 'fail'
|
|
362
|
-
// `verificationStatus` acima pausou antes daqui. 'pass' com stamp velho
|
|
363
|
-
// também já pausou, por `verificationStale`.
|
|
244
|
+
// 'fail' já pausou por `verificationStatus`; 'pass' com stamp velho, por `verificationStale`.
|
|
364
245
|
}
|
|
365
246
|
|
|
366
247
|
// Feature que gera mídia: custo, revisão humana, marcação e hot path só valem provados na seção.
|
|
@@ -375,19 +256,13 @@ export function shouldPauseGate(config, signals) {
|
|
|
375
256
|
};
|
|
376
257
|
}
|
|
377
258
|
|
|
378
|
-
//
|
|
379
|
-
// completo. É ele que faz paginação, ordenação, filtro e os três estados
|
|
380
|
-
// virarem cenário do plano — sem ele, uma lista de 2 000 linhas é aprovada
|
|
381
|
-
// listando tudo, e o implementador entrega exatamente o cenário.
|
|
259
|
+
// Sem o checklist de tela completo, paginação, ordenação, filtro e os três estados não viram cenário.
|
|
382
260
|
if (signals.gate === 'uiux') {
|
|
383
261
|
const reason = uiuxChecklistPause(signals.screenChecklist);
|
|
384
262
|
if (reason) return { pause: true, reason };
|
|
385
263
|
}
|
|
386
264
|
|
|
387
|
-
//
|
|
388
|
-
// decidiu itens de tela e o verificador não registrou a conferência deles
|
|
389
|
-
// contra a tela entregue. Cobra-se o item DEVIDO (`uncovered`): tela toda
|
|
390
|
-
// "não se aplica", mockup legado ou ausente não devem nada.
|
|
265
|
+
// Cobra-se só o item devido (`uncovered`): tela toda "não se aplica", design legado ou ausente não devem nada.
|
|
391
266
|
if (signals.gate === 'review' && (signals.screenChecklist?.uncovered?.length ?? 0) > 0) {
|
|
392
267
|
const { section, uncovered } = signals.screenChecklist;
|
|
393
268
|
if (section !== true) {
|
|
@@ -420,10 +295,6 @@ export function shouldPauseGate(config, signals) {
|
|
|
420
295
|
return { pause: false, reason: '' };
|
|
421
296
|
}
|
|
422
297
|
|
|
423
|
-
/**
|
|
424
|
-
* Human-readable list of risk signals present (for logging/Gate summary).
|
|
425
|
-
* @returns {string[]}
|
|
426
|
-
*/
|
|
427
298
|
export function getRiskSignals(signals) {
|
|
428
299
|
const out = [];
|
|
429
300
|
if (FORCED_PAUSE_WORK_TYPES.includes(signals.workType)) out.push('hotfix (gate humano obrigatório)');
|
|
@@ -432,8 +303,7 @@ export function getRiskSignals(signals) {
|
|
|
432
303
|
out.push(`standards violations: ${signals.standardsViolations.join(', ')}`);
|
|
433
304
|
}
|
|
434
305
|
if (signals.verificationStatus === 'fail') out.push('verify vermelho');
|
|
435
|
-
// Listado mesmo quando
|
|
436
|
-
// indistinguível de uma omissão. A classe diz de quem é a ausência de prova.
|
|
306
|
+
// Listado mesmo quando não pausa: dispensa silenciosa é indistinguível de omissão.
|
|
437
307
|
if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
|
|
438
308
|
const klass = classifyE2eSkip(signals.e2eReason);
|
|
439
309
|
const literal = typeof signals.e2eReason === 'string' && signals.e2eReason.trim() !== ''
|
|
@@ -445,9 +315,7 @@ export function getRiskSignals(signals) {
|
|
|
445
315
|
out.push('verification stamp desatualizado (stale) no ship');
|
|
446
316
|
}
|
|
447
317
|
if (signals.gate === 'review') out.push(reviewSummary(signals.reviewReports));
|
|
448
|
-
// Listado mesmo quando
|
|
449
|
-
// declara caminho de prompt/modelo é indistinguível, no relatório, de um que
|
|
450
|
-
// declara e não tocou em nada — e as duas coisas significam coisas diferentes.
|
|
318
|
+
// Listado mesmo quando não pausa, pela mesma razão do e2e.
|
|
451
319
|
if (signals.gate === 'review' && signals.promptModel) {
|
|
452
320
|
const { declared, touched } = signals.promptModel;
|
|
453
321
|
if (!declared) {
|
|
@@ -462,17 +330,12 @@ export function getRiskSignals(signals) {
|
|
|
462
330
|
if (typeof signals.sliceCount === 'number' && signals.sliceCount > MAX_SLICES) {
|
|
463
331
|
out.push(`${signals.sliceCount} fatias (acima de ${MAX_SLICES})`);
|
|
464
332
|
}
|
|
465
|
-
// Listado mesmo quando NÃO pausa, pela mesma razão do sinal irmão: a isenção
|
|
466
|
-
// de uma feature que não gera mídia tem de ser VISÍVEL, senão é
|
|
467
|
-
// indistinguível de uma omissão.
|
|
468
333
|
if (signals.gate === 'review' && signals.mediaClaims?.featureGeneratesMedia === true) {
|
|
469
334
|
out.push(signals.mediaClaims.section === true
|
|
470
335
|
? 'prova de mídia registrada'
|
|
471
336
|
: 'verifier.md sem a seção `## Mídia gerada` (prova de mídia ausente)');
|
|
472
337
|
}
|
|
473
338
|
|
|
474
|
-
// Listado mesmo quando NÃO pausa: a isenção (sem mockup, legado, nada
|
|
475
|
-
// devido) e o checklist conferido precisam ser distinguíveis de uma omissão.
|
|
476
339
|
if ((signals.gate === 'uiux' || signals.gate === 'review') && signals.screenChecklist) {
|
|
477
340
|
const { mockups, legacy, exempt, screens = [], problems = [], section, uncovered = [] } = signals.screenChecklist;
|
|
478
341
|
if (!mockups) {
|
|
@@ -483,13 +346,18 @@ export function getRiskSignals(signals) {
|
|
|
483
346
|
out.push(problems.length > 0
|
|
484
347
|
? `checklist de tela incompleto (${problems.length} problema(s))`
|
|
485
348
|
: `checklist de tela completo (${screens.length} tela(s))`);
|
|
349
|
+
const prototypes = signals.screenChecklist.prototypes ?? [];
|
|
350
|
+
if (screens.length > 0) {
|
|
351
|
+
out.push(prototypes.length > 0
|
|
352
|
+
? `protótipo incompleto em ${prototypes.length} tela(s)`
|
|
353
|
+
: `protótipo e prints 1280/390 presentes (${screens.length} tela(s))`);
|
|
354
|
+
}
|
|
486
355
|
} else if (uncovered.length > 0) {
|
|
487
356
|
out.push(section !== true
|
|
488
357
|
? 'verifier.md sem a seção `## Checklist de tela`'
|
|
489
358
|
: `checklist de tela sem conferência em ${uncovered.length} tela(s)`);
|
|
490
359
|
} else if (screens.length === 0) {
|
|
491
|
-
//
|
|
492
|
-
// não pode ler igual a uma tela toda "não se aplica".
|
|
360
|
+
// Sem tela nenhuma: nada a cobrar no ship, mas não pode ler igual a uma tela toda "não se aplica".
|
|
493
361
|
out.push(`checklist de tela: ${firstOf(problems)}`);
|
|
494
362
|
} else if (section === true) {
|
|
495
363
|
out.push(`checklist de tela conferido (${screens.length} tela(s))`);
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* The index of the ADRs that still hold across the project:
|
|
3
|
-
* `.morph/specs/adrs/`, one file per ADR, maintained by finish/archive alone,
|
|
4
|
-
* inside the archive commit, from the closing feature's `2-plan/plan.md`:
|
|
5
|
-
* - enters: an ADR of `## Decisões` with `**Alcance:** projeto`;
|
|
6
|
-
* - leaves: an ADR its `### Conflitos com o acervo` revokes in full (file deleted);
|
|
7
|
-
* - narrows: one it revokes in part keeps its file, with `scope:` where it still holds.
|
|
8
|
-
* One file per ADR so parallel features that index different ADRs merge cleanly.
|
|
9
|
-
*/
|
|
1
|
+
// One file per ADR under `.morph/specs/adrs/`, so parallel features that index different ADRs merge cleanly.
|
|
10
2
|
|
|
11
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
|
12
4
|
import { basename, join } from 'path';
|
|
@@ -48,11 +40,7 @@ function renderAdrFile({ ref, title, body }, feature) {
|
|
|
48
40
|
].join('\n');
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
/**
|
|
52
|
-
* The ADRs the index holds; `[]` when there is none or it cannot be read.
|
|
53
|
-
*
|
|
54
|
-
* @returns {Array<{ ref: string, file: string, title: string|null, scope: string|null }>}
|
|
55
|
-
*/
|
|
43
|
+
/** `[]` when there is no index or it cannot be read. */
|
|
56
44
|
export function readAdrIndex(cwd) {
|
|
57
45
|
const dir = join(cwd, ADR_INDEX_PATHSPEC);
|
|
58
46
|
try {
|
|
@@ -61,7 +49,7 @@ export function readAdrIndex(cwd) {
|
|
|
61
49
|
const ref = refOf(file);
|
|
62
50
|
if (!ref) return [];
|
|
63
51
|
let content = '';
|
|
64
|
-
try { content = readFileSync(join(dir, file), 'utf8'); } catch {
|
|
52
|
+
try { content = readFileSync(join(dir, file), 'utf8'); } catch {}
|
|
65
53
|
return [{ ref, file, title: readFrontmatterField(content, 'title'), scope: readFrontmatterField(content, 'scope') }];
|
|
66
54
|
});
|
|
67
55
|
} catch {
|
|
@@ -69,16 +57,7 @@ export function readAdrIndex(cwd) {
|
|
|
69
57
|
}
|
|
70
58
|
}
|
|
71
59
|
|
|
72
|
-
/**
|
|
73
|
-
* Apply a closing feature's plan to the index: its project-wide ADRs enter, and
|
|
74
|
-
* the ADRs its conflicts table revokes are deleted (total) or narrowed (parcial).
|
|
75
|
-
* An unreadable or absent plan touches nothing.
|
|
76
|
-
*
|
|
77
|
-
* @param {string} cwd
|
|
78
|
-
* @param {string} featureDir - the feature folder (already archived, at close time)
|
|
79
|
-
* @returns {{ changed: boolean, added: string[], revoked: string[], narrowed: string[],
|
|
80
|
-
* problems: Array<{ id: string|null, reason: string, message: string }> }}
|
|
81
|
-
*/
|
|
60
|
+
/** An unreadable or absent plan touches nothing. */
|
|
82
61
|
export function updateAdrIndex(cwd, featureDir) {
|
|
83
62
|
const result = { changed: false, added: [], revoked: [], narrowed: [], problems: [] };
|
|
84
63
|
const feature = basename(featureDir);
|
|
@@ -88,7 +67,7 @@ export function updateAdrIndex(cwd, featureDir) {
|
|
|
88
67
|
try {
|
|
89
68
|
const planPath = join(featureDir, PLAN.phaseDir, PLAN.filename);
|
|
90
69
|
if (existsSync(planPath)) plan = parsePlan(readFileSync(planPath, 'utf8'));
|
|
91
|
-
} catch {
|
|
70
|
+
} catch {}
|
|
92
71
|
if (!plan) return result;
|
|
93
72
|
|
|
94
73
|
for (const { id, title, body } of plan.adrs.filter((a) => a.scope === 'projeto')) {
|
|
@@ -128,14 +107,7 @@ export function updateAdrIndex(cwd, featureDir) {
|
|
|
128
107
|
return result;
|
|
129
108
|
}
|
|
130
109
|
|
|
131
|
-
/**
|
|
132
|
-
* What the finish would fail to apply from a plan's revocation table, reported
|
|
133
|
-
* while the plan can still change.
|
|
134
|
-
*
|
|
135
|
-
* @param {string} cwd
|
|
136
|
-
* @param {ReturnType<typeof parsePlan>} plan
|
|
137
|
-
* @returns {string[]}
|
|
138
|
-
*/
|
|
110
|
+
/** What the finish would fail to apply from the revocation table, reported while the plan can still change. */
|
|
139
111
|
export function revocationWarnings(cwd, plan) {
|
|
140
112
|
const { entries, problems } = parseRevocations(plan.conflicts?.text);
|
|
141
113
|
const warnings = problems.map((p) => `### Conflitos com o acervo: ${p.message}`);
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
const FRONTMATTER_RE = /^(?---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/;
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Set (or replace) a single top-level frontmatter field, preserving every
|
|
5
|
-
* other line, the original EOL style, and anything after the closing `---`.
|
|
6
|
-
* A document with no frontmatter block is returned unchanged.
|
|
7
|
-
*
|
|
8
|
-
* @param {string} content
|
|
9
|
-
* @param {string} key
|
|
10
|
-
* @param {string} value - written as-is; quote it first when it is free text
|
|
11
|
-
* @returns {string}
|
|
12
|
-
*/
|
|
3
|
+
/** Preserves every other line, the EOL style and the body; no frontmatter block → unchanged. `value` is written as-is. */
|
|
13
4
|
export function setFrontmatterField(content, key, value) {
|
|
14
5
|
const m = FRONTMATTER_RE.exec(content);
|
|
15
6
|
if (!m || m.index !== 0) return content;
|
|
@@ -25,11 +16,7 @@ export function setFrontmatterField(content, key, value) {
|
|
|
25
16
|
return open + next + close + content.slice(full.length);
|
|
26
17
|
}
|
|
27
18
|
|
|
28
|
-
/**
|
|
29
|
-
* Read a single top-level frontmatter field; `null` when absent or there is no
|
|
30
|
-
* frontmatter block. A double-quoted value is read as the JSON string it is
|
|
31
|
-
* written as; a single-quoted one as YAML reads it (`''` is a quote).
|
|
32
|
-
*/
|
|
19
|
+
/** `null` when absent; double-quoted values read as JSON strings, single-quoted as YAML (`''` is a quote). */
|
|
33
20
|
export function readFrontmatterField(content, key) {
|
|
34
21
|
const m = FRONTMATTER_RE.exec(content);
|
|
35
22
|
if (!m || m.index !== 0) return null;
|
|
@@ -4,13 +4,7 @@ export const SEPARATOR_ROW_RE = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s
|
|
|
4
4
|
/** A line that belongs to a table: it carries an unescaped pipe (GFM does not require the leading one). */
|
|
5
5
|
export const isTableLine = (line) => /(?<!\\)\|/.test(line);
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Split a table row on unescaped pipes, outer pipes optional; `\|` inside a
|
|
9
|
-
* cell is a literal pipe.
|
|
10
|
-
*
|
|
11
|
-
* @param {string} row
|
|
12
|
-
* @returns {string[]} trimmed cells
|
|
13
|
-
*/
|
|
7
|
+
/** Splits on unescaped pipes, outer pipes optional; `\|` inside a cell is a literal pipe. */
|
|
14
8
|
export function tableCells(row) {
|
|
15
9
|
const inner = row.trim().replace(/^\|/, '').replace(/(?<!\\)\|$/, '');
|
|
16
10
|
return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* The ADRs of the project index that a plan revokes: the table under
|
|
3
|
-
* `### Conflitos com o acervo` whose first header cell is `ADR`, with columns
|
|
4
|
-
* `{feature}/ADR-{nnn}`, `total|parcial` and the scope where the old one still holds.
|
|
5
|
-
* plan-check reports the problems while the plan can still change (it freezes
|
|
6
|
-
* at the plan gate); nothing ambiguous reaches the finish.
|
|
7
|
-
*/
|
|
1
|
+
// plan-check reports the problems while the plan can still change, so nothing ambiguous reaches the finish.
|
|
8
2
|
|
|
9
3
|
import { SEPARATOR_ROW_RE, isTableLine, tableCells } from './gfm-table.js';
|
|
10
4
|
|
|
@@ -46,13 +40,6 @@ function readRow(line) {
|
|
|
46
40
|
return { entry: problems.length ? { id } : { id, change, scope }, problems };
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
/**
|
|
50
|
-
* @param {string|null} conflicts - the text of `### Conflitos com o acervo` (`parsePlan(...).conflicts.text`)
|
|
51
|
-
* @returns {{
|
|
52
|
-
* entries: Array<{ id: string, change: 'total'|'parcial', scope: string|null }>,
|
|
53
|
-
* problems: Array<{ id: string|null, reason: string, message: string }>
|
|
54
|
-
* }}
|
|
55
|
-
*/
|
|
56
43
|
export function parseRevocations(conflicts) {
|
|
57
44
|
const problems = [];
|
|
58
45
|
const rows = [];
|