@polymorphism-tech/morph-spec 9.0.0 → 9.1.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 +4 -62
- 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 +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- 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 +11 -67
- 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/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/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 +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- 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 +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- 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 +34 -12
- 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/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/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- 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 +14 -96
- 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 +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- 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/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/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 +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- 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/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -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/validators/code-budget/code-budget-validator.js +3 -43
- 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 +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- 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 +28 -275
- 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/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,23 @@
|
|
|
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'],
|
|
54
14
|
];
|
|
55
15
|
|
|
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.
|
|
16
|
+
// `unknown` (razão vazia) pausa de propósito: dispensa sem motivo é o no-op silencioso que a regra elimina.
|
|
60
17
|
const E2E_SKIP_CLASSES_THAT_PAUSE = ['environment', 'requested', 'unknown'];
|
|
61
18
|
|
|
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
|
-
*/
|
|
19
|
+
// `project` diz que o repositório não tem runtime (dispensa legítima); `environment`/`requested` falam
|
|
20
|
+
// da máquina ou do operador, nunca do código. Razão vazia é `unknown`, jamais dispensa.
|
|
79
21
|
export function classifyE2eSkip(reason) {
|
|
80
22
|
const text = typeof reason === 'string' ? reason.trim() : '';
|
|
81
23
|
if (text === '') return 'unknown';
|
|
@@ -85,7 +27,6 @@ export function classifyE2eSkip(reason) {
|
|
|
85
27
|
return 'unknown';
|
|
86
28
|
}
|
|
87
29
|
|
|
88
|
-
/** Mensagem de pausa por classe — nomeia SEMPRE a razão literal do verify. */
|
|
89
30
|
function e2ePauseReason(klass, reason) {
|
|
90
31
|
const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : null;
|
|
91
32
|
if (klass === 'environment') {
|
|
@@ -102,38 +43,32 @@ function e2ePauseReason(klass, reason) {
|
|
|
102
43
|
return `e2e pulado por razão não classificada: "${literal}" — sem classificação, não há dispensa`;
|
|
103
44
|
}
|
|
104
45
|
|
|
105
|
-
/** A razão gravada no sinal quando o checklist de tela isenta um mockup legado. */
|
|
106
46
|
export const LEGACY_MOCKUP_EXEMPTION = 'checklist dispensado: mockup no formato antigo';
|
|
107
47
|
|
|
108
|
-
/** Os dois primeiros itens de uma lista, e quantos ficaram de fora. */
|
|
109
48
|
function firstOf(items) {
|
|
110
49
|
const shown = items.slice(0, 2).join('; ');
|
|
111
50
|
return items.length > 2 ? `${shown} (+${items.length - 2})` : shown;
|
|
112
51
|
}
|
|
113
52
|
|
|
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
|
-
*/
|
|
53
|
+
// Exportada porque approve/advance `auto` recusam pela mesma regra. Sem design de tela não conta,
|
|
54
|
+
// e o legado chega sem problemas: a auditoria o isenta. Checklist
|
|
55
|
+
// completo ainda deve o protótipo e os prints 1280/390 (`prototypes`).
|
|
125
56
|
export function uiuxChecklistPause(screenChecklist) {
|
|
126
57
|
if (screenChecklist?.mockups !== true) return null;
|
|
127
58
|
const problems = screenChecklist.problems ?? [];
|
|
128
|
-
if (problems.length
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
59
|
+
if (problems.length > 0) {
|
|
60
|
+
return `checklist de tela incompleto em 1-design/mockups.md: ${firstOf(problems)}`
|
|
61
|
+
+ ' — volume, paginação, ordenação, filtro, os três estados, mobile e abas por tela'
|
|
62
|
+
+ ' ("não se aplica" vale, se escrito)';
|
|
63
|
+
}
|
|
64
|
+
const prototypes = screenChecklist.prototypes ?? [];
|
|
65
|
+
if (prototypes.length === 0) return null;
|
|
66
|
+
return `protótipo incompleto: ${firstOf(prototypes)}`
|
|
67
|
+
+ ' — cada tela aponta o HTML renderizado e deixa os prints de 1280 e 390 ao lado dele';
|
|
132
68
|
}
|
|
133
69
|
|
|
134
70
|
const ROLE_PERSONA = { verifier: 'morph-verifier', reviewer: 'morph-reviewer', security: 'morph-security' };
|
|
135
71
|
|
|
136
|
-
/** Por que os relatórios de review seguram o ship, ou null. */
|
|
137
72
|
export function reviewPause(signal) {
|
|
138
73
|
const required = signal?.required ?? ['verifier'];
|
|
139
74
|
const reports = signal?.reports ?? {};
|
|
@@ -190,14 +125,7 @@ function reviewSummary(signal) {
|
|
|
190
125
|
return `review: ${verdicts}; ${blockers} bloqueador(es)${checks}`;
|
|
191
126
|
}
|
|
192
127
|
|
|
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
|
-
*/
|
|
128
|
+
// Caminho sensível não muda o verificador: despacha a persona `security` (`securityReviewFor`).
|
|
201
129
|
export function verifierModelFor({ promptModelTouched = false, generatesMedia = false } = {}) {
|
|
202
130
|
const reasons = [];
|
|
203
131
|
if (promptModelTouched) reasons.push('prompt/modelo alterado');
|
|
@@ -205,13 +133,6 @@ export function verifierModelFor({ promptModelTouched = false, generatesMedia =
|
|
|
205
133
|
return { model: reasons.length ? 'opus' : 'sonnet', reasons };
|
|
206
134
|
}
|
|
207
135
|
|
|
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
136
|
export function securityReviewFor(changedFiles = []) {
|
|
216
137
|
const risky = changedFiles.filter((f) => SECURITY_PATH_RE.test(f.replaceAll('\\', '/')));
|
|
217
138
|
return { required: risky.length > 0, reasons: risky.length ? [`caminho sensível: ${firstOf(risky)}`] : [] };
|
|
@@ -224,27 +145,13 @@ const PLAN_CHECKS_REASONS = {
|
|
|
224
145
|
stale: '2-plan/checks.json não corresponde ao plan.md atual',
|
|
225
146
|
};
|
|
226
147
|
|
|
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
|
-
*/
|
|
148
|
+
// Regra única do gate-check (pausa) e do approve/advance automáticos (recusa).
|
|
237
149
|
export function planChecksPause(status) {
|
|
238
150
|
if (status === undefined || status === 'fresh') return null;
|
|
239
151
|
const what = PLAN_CHECKS_REASONS[status] ?? `checks.json em estado "${status}"`;
|
|
240
152
|
return `${what} — rode \`morph-spec plan-check {feature}\` até zero erro antes do gate do plano`;
|
|
241
153
|
}
|
|
242
154
|
|
|
243
|
-
/**
|
|
244
|
-
* @param {object} config - .morph/config/config.json contents (uses `trust`)
|
|
245
|
-
* @param {object} signals
|
|
246
|
-
* @returns {{ pause: boolean, reason: string }}
|
|
247
|
-
*/
|
|
248
155
|
export function shouldPauseGate(config, signals) {
|
|
249
156
|
const trust = config?.trust ?? 'auto';
|
|
250
157
|
|
|
@@ -270,10 +177,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
270
177
|
};
|
|
271
178
|
}
|
|
272
179
|
|
|
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).
|
|
180
|
+
// Só 'fail' pausa aqui: features antigas não têm o stamp.
|
|
277
181
|
if (signals.verificationStatus === 'fail') {
|
|
278
182
|
return {
|
|
279
183
|
pause: true,
|
|
@@ -281,7 +185,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
281
185
|
};
|
|
282
186
|
}
|
|
283
187
|
|
|
284
|
-
// No ship, ausência de stamp não é
|
|
188
|
+
// No ship, ausência de stamp não é verde.
|
|
285
189
|
if (signals.gate === 'review' && !signals.verificationStatus) {
|
|
286
190
|
return {
|
|
287
191
|
pause: true,
|
|
@@ -289,16 +193,8 @@ export function shouldPauseGate(config, signals) {
|
|
|
289
193
|
};
|
|
290
194
|
}
|
|
291
195
|
|
|
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.
|
|
196
|
+
// Skip não reprova o rollup, então só a razão literal separa dispensa de ausência de prova.
|
|
197
|
+
// Só no ship: pausar plan/uiux numa máquina sem docker travaria planejamento legítimo.
|
|
302
198
|
if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
|
|
303
199
|
const klass = classifyE2eSkip(signals.e2eReason);
|
|
304
200
|
if (E2E_SKIP_CLASSES_THAT_PAUSE.includes(klass)) {
|
|
@@ -306,13 +202,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
306
202
|
}
|
|
307
203
|
}
|
|
308
204
|
|
|
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.
|
|
205
|
+
// Regra de ship, não de gate em geral; stamp ausente é fail-open (`false`) em collectSignals.
|
|
316
206
|
if (signals.gate === 'review' && signals.verificationStale === true) {
|
|
317
207
|
return {
|
|
318
208
|
pause: true,
|
|
@@ -325,18 +215,9 @@ export function shouldPauseGate(config, signals) {
|
|
|
325
215
|
if (reason) return { pause: true, reason };
|
|
326
216
|
}
|
|
327
217
|
|
|
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.
|
|
218
|
+
// Prompt e modelo não são lidos por build nem por teste. Depois dos relatórios de review de propósito:
|
|
219
|
+
// relatório ausente é o defeito mais grave. Pausa, nunca bloqueia: bloquear tornaria inaprovável
|
|
220
|
+
// toda feature legada.
|
|
340
221
|
if (signals.gate === 'review' && (signals.promptModel?.touched?.length ?? 0) > 0) {
|
|
341
222
|
const touched = signals.promptModel.touched;
|
|
342
223
|
const where = `${touched.slice(0, 3).join(', ')}${touched.length > 3 ? ` (+${touched.length - 3})` : ''}`;
|
|
@@ -358,9 +239,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
358
239
|
+ ' — rode `morph-spec verify {feature}` (escopo de feature) antes do ship',
|
|
359
240
|
};
|
|
360
241
|
}
|
|
361
|
-
// 'fail'
|
|
362
|
-
// `verificationStatus` acima pausou antes daqui. 'pass' com stamp velho
|
|
363
|
-
// também já pausou, por `verificationStale`.
|
|
242
|
+
// 'fail' já pausou por `verificationStatus`; 'pass' com stamp velho, por `verificationStale`.
|
|
364
243
|
}
|
|
365
244
|
|
|
366
245
|
// Feature que gera mídia: custo, revisão humana, marcação e hot path só valem provados na seção.
|
|
@@ -375,19 +254,13 @@ export function shouldPauseGate(config, signals) {
|
|
|
375
254
|
};
|
|
376
255
|
}
|
|
377
256
|
|
|
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.
|
|
257
|
+
// Sem o checklist de tela completo, paginação, ordenação, filtro e os três estados não viram cenário.
|
|
382
258
|
if (signals.gate === 'uiux') {
|
|
383
259
|
const reason = uiuxChecklistPause(signals.screenChecklist);
|
|
384
260
|
if (reason) return { pause: true, reason };
|
|
385
261
|
}
|
|
386
262
|
|
|
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.
|
|
263
|
+
// Cobra-se só o item devido (`uncovered`): tela toda "não se aplica", design legado ou ausente não devem nada.
|
|
391
264
|
if (signals.gate === 'review' && (signals.screenChecklist?.uncovered?.length ?? 0) > 0) {
|
|
392
265
|
const { section, uncovered } = signals.screenChecklist;
|
|
393
266
|
if (section !== true) {
|
|
@@ -420,10 +293,6 @@ export function shouldPauseGate(config, signals) {
|
|
|
420
293
|
return { pause: false, reason: '' };
|
|
421
294
|
}
|
|
422
295
|
|
|
423
|
-
/**
|
|
424
|
-
* Human-readable list of risk signals present (for logging/Gate summary).
|
|
425
|
-
* @returns {string[]}
|
|
426
|
-
*/
|
|
427
296
|
export function getRiskSignals(signals) {
|
|
428
297
|
const out = [];
|
|
429
298
|
if (FORCED_PAUSE_WORK_TYPES.includes(signals.workType)) out.push('hotfix (gate humano obrigatório)');
|
|
@@ -432,8 +301,7 @@ export function getRiskSignals(signals) {
|
|
|
432
301
|
out.push(`standards violations: ${signals.standardsViolations.join(', ')}`);
|
|
433
302
|
}
|
|
434
303
|
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.
|
|
304
|
+
// Listado mesmo quando não pausa: dispensa silenciosa é indistinguível de omissão.
|
|
437
305
|
if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
|
|
438
306
|
const klass = classifyE2eSkip(signals.e2eReason);
|
|
439
307
|
const literal = typeof signals.e2eReason === 'string' && signals.e2eReason.trim() !== ''
|
|
@@ -445,9 +313,7 @@ export function getRiskSignals(signals) {
|
|
|
445
313
|
out.push('verification stamp desatualizado (stale) no ship');
|
|
446
314
|
}
|
|
447
315
|
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.
|
|
316
|
+
// Listado mesmo quando não pausa, pela mesma razão do e2e.
|
|
451
317
|
if (signals.gate === 'review' && signals.promptModel) {
|
|
452
318
|
const { declared, touched } = signals.promptModel;
|
|
453
319
|
if (!declared) {
|
|
@@ -462,17 +328,12 @@ export function getRiskSignals(signals) {
|
|
|
462
328
|
if (typeof signals.sliceCount === 'number' && signals.sliceCount > MAX_SLICES) {
|
|
463
329
|
out.push(`${signals.sliceCount} fatias (acima de ${MAX_SLICES})`);
|
|
464
330
|
}
|
|
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
331
|
if (signals.gate === 'review' && signals.mediaClaims?.featureGeneratesMedia === true) {
|
|
469
332
|
out.push(signals.mediaClaims.section === true
|
|
470
333
|
? 'prova de mídia registrada'
|
|
471
334
|
: 'verifier.md sem a seção `## Mídia gerada` (prova de mídia ausente)');
|
|
472
335
|
}
|
|
473
336
|
|
|
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
337
|
if ((signals.gate === 'uiux' || signals.gate === 'review') && signals.screenChecklist) {
|
|
477
338
|
const { mockups, legacy, exempt, screens = [], problems = [], section, uncovered = [] } = signals.screenChecklist;
|
|
478
339
|
if (!mockups) {
|
|
@@ -483,13 +344,18 @@ export function getRiskSignals(signals) {
|
|
|
483
344
|
out.push(problems.length > 0
|
|
484
345
|
? `checklist de tela incompleto (${problems.length} problema(s))`
|
|
485
346
|
: `checklist de tela completo (${screens.length} tela(s))`);
|
|
347
|
+
const prototypes = signals.screenChecklist.prototypes ?? [];
|
|
348
|
+
if (screens.length > 0) {
|
|
349
|
+
out.push(prototypes.length > 0
|
|
350
|
+
? `protótipo incompleto em ${prototypes.length} tela(s)`
|
|
351
|
+
: `protótipo e prints 1280/390 presentes (${screens.length} tela(s))`);
|
|
352
|
+
}
|
|
486
353
|
} else if (uncovered.length > 0) {
|
|
487
354
|
out.push(section !== true
|
|
488
355
|
? 'verifier.md sem a seção `## Checklist de tela`'
|
|
489
356
|
: `checklist de tela sem conferência em ${uncovered.length} tela(s)`);
|
|
490
357
|
} else if (screens.length === 0) {
|
|
491
|
-
//
|
|
492
|
-
// não pode ler igual a uma tela toda "não se aplica".
|
|
358
|
+
// Sem tela nenhuma: nada a cobrar no ship, mas não pode ler igual a uma tela toda "não se aplica".
|
|
493
359
|
out.push(`checklist de tela: ${firstOf(problems)}`);
|
|
494
360
|
} else if (section === true) {
|
|
495
361
|
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 = [];
|