@polymorphism-tech/morph-spec 8.47.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 +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- 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 +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- 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 +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- 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.py +55 -41
- package/framework/hooks/package.json +3 -0
- 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 +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- 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 +14 -72
- 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 +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- 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/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- 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/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- 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 +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- 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/evals-with-cache.md +1 -2
- 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 +3 -3
- 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 +7 -7
- 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/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- 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 +18 -105
- 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 +17 -111
- 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-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- 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 +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- 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 +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- 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 +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- 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 +4 -7
- 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 +14 -13
- 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 +8 -59
- 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 +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- 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 +2 -1
- 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 +17 -17
- 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 +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- 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 +4 -26
- 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 +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- 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 +31 -126
- 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 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/dotnet-test-introspection.js +0 -632
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,36 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
* The index of the ADRs that still hold across the project:
|
|
3
|
-
* `.morph/specs/adrs/`, one file per ADR, committed next to the use-case acervo.
|
|
4
|
-
*
|
|
5
|
-
* Every feature writes ADRs into its own `decisions.md`, and most of them only
|
|
6
|
-
* matter inside that feature. The index keeps the few that set a project-wide
|
|
7
|
-
* rule, so brainstorming and plan read one folder instead of every archived
|
|
8
|
-
* `decisions.md` — those stay as history, not as instruction.
|
|
9
|
-
*
|
|
10
|
-
* One file per ADR (`{feature}--ADR-{nnn}.md`), like the use-case acervo:
|
|
11
|
-
* parallel features that index different ADRs never touch the same file, so
|
|
12
|
-
* their archive commits merge cleanly — a single shared page conflicted on the
|
|
13
|
-
* second merge.
|
|
14
|
-
*
|
|
15
|
-
* Maintained by finish/archive alone, inside the archive commit:
|
|
16
|
-
* - enters: an ADR of the closing feature whose block carries `**Alcance:** projeto`;
|
|
17
|
-
* - leaves: an ADR the closing feature's `spec.md` revokes in full (file deleted);
|
|
18
|
-
* - narrows: one it revokes in part keeps its file, with `scope:` where it still holds.
|
|
19
|
-
* Nothing to add or revoke → nothing is touched (nor created).
|
|
20
|
-
*/
|
|
1
|
+
// One file per ADR under `.morph/specs/adrs/`, so parallel features that index different ADRs merge cleanly.
|
|
21
2
|
|
|
22
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
|
23
4
|
import { basename, join } from 'path';
|
|
5
|
+
import { readFrontmatterField, setFrontmatterField } from './frontmatter.js';
|
|
6
|
+
import { parseRevocations } from './revocations.js';
|
|
7
|
+
import { parsePlan } from '../plan/plan-parser.js';
|
|
24
8
|
import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
|
|
25
|
-
import { readFrontmatterField, setFrontmatterField } from './usecase-promoter.js';
|
|
26
9
|
|
|
27
10
|
/** Project-relative path of the index folder (git pathspec-ready). */
|
|
28
11
|
export const ADR_INDEX_PATHSPEC = join('.morph', 'specs', 'adrs');
|
|
29
12
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
/** `**Alcance:** projeto` — also `**Alcance**: projeto`. */
|
|
33
|
-
const PROJECT_SCOPE_RE = /^\s*\*\*Alcance:?\*\*:?\s*projeto\b/i;
|
|
13
|
+
const PLAN = OUTPUT_SCHEMA.plan;
|
|
14
|
+
const INDEX_LABEL = '.morph/specs/adrs/';
|
|
34
15
|
const INDEX_FILE_RE = /^([a-z0-9][a-z0-9-]*)--(ADR-\d+)\.md$/;
|
|
35
16
|
|
|
36
17
|
/** `admin/ADR-001` ↔ `admin--ADR-001.md`. */
|
|
@@ -40,61 +21,26 @@ const refOf = (name) => {
|
|
|
40
21
|
return m ? `${m[1]}/${m[2]}` : null;
|
|
41
22
|
};
|
|
42
23
|
|
|
43
|
-
/**
|
|
44
|
-
* The ADRs of one `decisions.md` marked as project-wide, each with its block.
|
|
45
|
-
*
|
|
46
|
-
* @param {string} content
|
|
47
|
-
* @returns {Array<{ adr: string, title: string, body: string }>}
|
|
48
|
-
*/
|
|
49
|
-
export function projectAdrs(content) {
|
|
50
|
-
const out = [];
|
|
51
|
-
let current = null; // { adr, title, level, marked, lines }
|
|
52
|
-
const close = () => {
|
|
53
|
-
if (current?.marked) out.push({ adr: current.adr, title: current.title, body: current.lines.join('\n').trim() });
|
|
54
|
-
current = null;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
for (const line of String(content ?? '').split(/\r?\n/)) {
|
|
58
|
-
const adr = ADR_HEADING_RE.exec(line);
|
|
59
|
-
if (adr) {
|
|
60
|
-
close();
|
|
61
|
-
current = { adr: adr[2], title: adr[3], level: adr[1].length, marked: false, lines: [] };
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const heading = ANY_HEADING_RE.exec(line);
|
|
65
|
-
if (current && heading && heading[1].length <= current.level) { close(); continue; }
|
|
66
|
-
if (!current) continue;
|
|
67
|
-
if (PROJECT_SCOPE_RE.test(line)) current.marked = true;
|
|
68
|
-
current.lines.push(line);
|
|
69
|
-
}
|
|
70
|
-
close();
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
24
|
function renderAdrFile({ ref, title, body }, feature) {
|
|
75
|
-
const
|
|
25
|
+
const source = `.morph/archive/${feature}/${PLAN.phaseDir}/${PLAN.filename}`;
|
|
76
26
|
return [
|
|
77
27
|
'---',
|
|
78
28
|
`adr: ${ref}`,
|
|
79
29
|
`title: ${JSON.stringify(title)}`,
|
|
80
|
-
`source: ${
|
|
30
|
+
`source: ${source}`,
|
|
81
31
|
'---',
|
|
82
32
|
'',
|
|
83
33
|
`# ${ref}: ${title}`,
|
|
84
34
|
'',
|
|
85
|
-
'> Mantido pelo `morph-spec finish` — não edite à mão. Vale no projeto até que o `
|
|
86
|
-
'> feature o revogue
|
|
35
|
+
'> Mantido pelo `morph-spec finish` — não edite à mão. Vale no projeto até que o `plan.md` de uma',
|
|
36
|
+
'> feature o revogue em `### Conflitos com o acervo`: por inteiro, e este arquivo sai; em parte, e `scope:` diz onde ele ainda vale.',
|
|
87
37
|
'',
|
|
88
38
|
body,
|
|
89
39
|
'',
|
|
90
40
|
].join('\n');
|
|
91
41
|
}
|
|
92
42
|
|
|
93
|
-
/**
|
|
94
|
-
* The ADRs the index holds; `[]` when there is none or it cannot be read.
|
|
95
|
-
*
|
|
96
|
-
* @returns {Array<{ ref: string, file: string, title: string|null, scope: string|null }>}
|
|
97
|
-
*/
|
|
43
|
+
/** `[]` when there is no index or it cannot be read. */
|
|
98
44
|
export function readAdrIndex(cwd) {
|
|
99
45
|
const dir = join(cwd, ADR_INDEX_PATHSPEC);
|
|
100
46
|
try {
|
|
@@ -103,7 +49,7 @@ export function readAdrIndex(cwd) {
|
|
|
103
49
|
const ref = refOf(file);
|
|
104
50
|
if (!ref) return [];
|
|
105
51
|
let content = '';
|
|
106
|
-
try { content = readFileSync(join(dir, file), 'utf8'); } catch {
|
|
52
|
+
try { content = readFileSync(join(dir, file), 'utf8'); } catch {}
|
|
107
53
|
return [{ ref, file, title: readFrontmatterField(content, 'title'), scope: readFrontmatterField(content, 'scope') }];
|
|
108
54
|
});
|
|
109
55
|
} catch {
|
|
@@ -111,30 +57,21 @@ export function readAdrIndex(cwd) {
|
|
|
111
57
|
}
|
|
112
58
|
}
|
|
113
59
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
* ADR revocations its `spec.md` declared delete a file (total) or narrow it
|
|
117
|
-
* (parcial).
|
|
118
|
-
*
|
|
119
|
-
* @param {string} cwd
|
|
120
|
-
* @param {string} featureDir - the feature folder (already archived, at close time)
|
|
121
|
-
* @param {Array<{ id: string, change: 'total'|'parcial', scope: string|null }>} revocations - ADR entries of `parseRevocations`
|
|
122
|
-
* @returns {{ changed: boolean, added: string[], revoked: string[], narrowed: string[],
|
|
123
|
-
* problems: Array<{ id: string, reason: string, message: string }> }}
|
|
124
|
-
*/
|
|
125
|
-
export function updateAdrIndex(cwd, featureDir, revocations = []) {
|
|
60
|
+
/** An unreadable or absent plan touches nothing. */
|
|
61
|
+
export function updateAdrIndex(cwd, featureDir) {
|
|
126
62
|
const result = { changed: false, added: [], revoked: [], narrowed: [], problems: [] };
|
|
127
63
|
const feature = basename(featureDir);
|
|
128
64
|
const dir = join(cwd, ADR_INDEX_PATHSPEC);
|
|
129
65
|
|
|
130
|
-
let
|
|
66
|
+
let plan = null;
|
|
131
67
|
try {
|
|
132
|
-
const
|
|
133
|
-
if (existsSync(
|
|
134
|
-
} catch {
|
|
68
|
+
const planPath = join(featureDir, PLAN.phaseDir, PLAN.filename);
|
|
69
|
+
if (existsSync(planPath)) plan = parsePlan(readFileSync(planPath, 'utf8'));
|
|
70
|
+
} catch {}
|
|
71
|
+
if (!plan) return result;
|
|
135
72
|
|
|
136
|
-
for (const {
|
|
137
|
-
const ref = `${feature}/${
|
|
73
|
+
for (const { id, title, body } of plan.adrs.filter((a) => a.scope === 'projeto')) {
|
|
74
|
+
const ref = `${feature}/${id}`;
|
|
138
75
|
const path = join(dir, adrFileName(ref));
|
|
139
76
|
const next = renderAdrFile({ ref, title, body }, feature);
|
|
140
77
|
const existed = existsSync(path);
|
|
@@ -145,13 +82,15 @@ export function updateAdrIndex(cwd, featureDir, revocations = []) {
|
|
|
145
82
|
if (!existed) result.added.push(ref);
|
|
146
83
|
}
|
|
147
84
|
|
|
85
|
+
const { entries: revocations, problems } = parseRevocations(plan.conflicts?.text);
|
|
86
|
+
result.problems.push(...problems);
|
|
148
87
|
for (const rev of revocations) {
|
|
149
88
|
const path = join(dir, adrFileName(rev.id));
|
|
150
89
|
if (!existsSync(path)) {
|
|
151
90
|
result.problems.push({
|
|
152
91
|
id: rev.id,
|
|
153
92
|
reason: 'adr-not-indexed',
|
|
154
|
-
message: `${rev.id} não está no índice de ADRs ativos (${
|
|
93
|
+
message: `${rev.id} não está no índice de ADRs ativos (${INDEX_LABEL}) — nada a retirar; o plan.md arquivado segue como história`,
|
|
155
94
|
});
|
|
156
95
|
continue;
|
|
157
96
|
}
|
|
@@ -167,3 +106,17 @@ export function updateAdrIndex(cwd, featureDir, revocations = []) {
|
|
|
167
106
|
|
|
168
107
|
return result;
|
|
169
108
|
}
|
|
109
|
+
|
|
110
|
+
/** What the finish would fail to apply from the revocation table, reported while the plan can still change. */
|
|
111
|
+
export function revocationWarnings(cwd, plan) {
|
|
112
|
+
const { entries, problems } = parseRevocations(plan.conflicts?.text);
|
|
113
|
+
const warnings = problems.map((p) => `### Conflitos com o acervo: ${p.message}`);
|
|
114
|
+
if (entries.length === 0) return warnings;
|
|
115
|
+
const indexed = new Set(readAdrIndex(cwd).map((a) => a.ref));
|
|
116
|
+
for (const e of entries) {
|
|
117
|
+
if (!indexed.has(e.id)) {
|
|
118
|
+
warnings.push(`### Conflitos com o acervo: ${e.id} não está no índice de ADRs ativos (${INDEX_LABEL}) — o finish não terá o que retirar`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return warnings;
|
|
122
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { templateSourcePath, writeIfAbsent } from '../../core/templates/template-seed.js';
|
|
4
|
+
import { renderTemplateString } from '../../core/templates/template-renderer.js';
|
|
5
|
+
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
6
|
+
|
|
7
|
+
export function seedDesignArtifacts(featureName, cwd = process.cwd()) {
|
|
8
|
+
const mockupsTemplate = readFileSync(templateSourcePath('ui-mockups', cwd), 'utf8');
|
|
9
|
+
const mockups = writeIfAbsent(
|
|
10
|
+
getAbsoluteOutputPath(cwd, featureName, 'uiMockups'),
|
|
11
|
+
renderTemplateString(mockupsTemplate, { FEATURE_NAME: featureName, DATE: new Date().toISOString().slice(0, 10) }),
|
|
12
|
+
);
|
|
13
|
+
const designSystem = writeIfAbsent(
|
|
14
|
+
join(cwd, '.morph', 'context', 'design-system.md'),
|
|
15
|
+
readFileSync(templateSourcePath('context-design-system', cwd), 'utf8'),
|
|
16
|
+
);
|
|
17
|
+
return { mockups, designSystem };
|
|
18
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const FRONTMATTER_RE = /^(?---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/;
|
|
2
|
+
|
|
3
|
+
/** Preserves every other line, the EOL style and the body; no frontmatter block → unchanged. `value` is written as-is. */
|
|
4
|
+
export function setFrontmatterField(content, key, value) {
|
|
5
|
+
const m = FRONTMATTER_RE.exec(content);
|
|
6
|
+
if (!m || m.index !== 0) return content;
|
|
7
|
+
|
|
8
|
+
const [full, open, body, close] = m;
|
|
9
|
+
const eol = full.includes('\r\n') ? '\r\n' : '\n';
|
|
10
|
+
const fieldLine = new RegExp(`^${key}:[ \\t]*.*$`, 'm');
|
|
11
|
+
// Function replacer: a value carrying `$&` or `$1` is text, not a pattern.
|
|
12
|
+
const next = fieldLine.test(body)
|
|
13
|
+
? body.replace(fieldLine, () => `${key}: ${value}`)
|
|
14
|
+
: `${body}${eol}${key}: ${value}`;
|
|
15
|
+
|
|
16
|
+
return open + next + close + content.slice(full.length);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** `null` when absent; double-quoted values read as JSON strings, single-quoted as YAML (`''` is a quote). */
|
|
20
|
+
export function readFrontmatterField(content, key) {
|
|
21
|
+
const m = FRONTMATTER_RE.exec(content);
|
|
22
|
+
if (!m || m.index !== 0) return null;
|
|
23
|
+
const line = new RegExp(`^${key}:[ \\t]*(.+?)[ \\t]*$`, 'm').exec(m[2]);
|
|
24
|
+
if (!line) return null;
|
|
25
|
+
const raw = line[1].trim();
|
|
26
|
+
if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"')) {
|
|
27
|
+
try { return JSON.parse(raw); } catch { return raw; }
|
|
28
|
+
}
|
|
29
|
+
if (raw.length >= 2 && raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1).replace(/''/g, "'");
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A markdown table separator row: `|---|:--:|`, with or without the outer pipes. */
|
|
2
|
+
export const SEPARATOR_ROW_RE = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
|
|
3
|
+
|
|
4
|
+
/** A line that belongs to a table: it carries an unescaped pipe (GFM does not require the leading one). */
|
|
5
|
+
export const isTableLine = (line) => /(?<!\\)\|/.test(line);
|
|
6
|
+
|
|
7
|
+
/** Splits on unescaped pipes, outer pipes optional; `\|` inside a cell is a literal pipe. */
|
|
8
|
+
export function tableCells(row) {
|
|
9
|
+
const inner = row.trim().replace(/^\|/, '').replace(/(?<!\\)\|$/, '');
|
|
10
|
+
return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
|
|
11
|
+
}
|
|
@@ -1,156 +1,85 @@
|
|
|
1
|
-
|
|
2
|
-
* The plan's declaration of what a feature supersedes in the project's acervo:
|
|
3
|
-
* the "UCs e ADRs que esta feature altera ou revoga" section of `spec.md`.
|
|
4
|
-
*
|
|
5
|
-
* ## UCs e ADRs que esta feature altera ou revoga
|
|
6
|
-
*
|
|
7
|
-
* | Id | Mudança | Substituto | Escopo em que o antigo ainda vale |
|
|
8
|
-
* |---|---|---|---|
|
|
9
|
-
* | UC-{nn} | parcial | UC-{nn} | {onde o antigo ainda vale} |
|
|
10
|
-
* | {feature}/ADR-{nnn} | total | — | — |
|
|
11
|
-
*
|
|
12
|
-
* Columns are positional, so the header wording is free. Parsing is pure: the
|
|
13
|
-
* finish applies the entries (`acervo-lifecycle.js`), and `dag`/`usecase
|
|
14
|
-
* check` report the problems while `spec.md` can still be edited — it freezes
|
|
15
|
-
* at Gate 2, so a bad row found at close time can no longer be fixed there.
|
|
16
|
-
*
|
|
17
|
-
* Nothing ambiguous is applied: a row with any problem, and every row of an id
|
|
18
|
-
* declared twice, stays out of `entries`. A table inside a code fence is an
|
|
19
|
-
* example, never a declaration.
|
|
20
|
-
*/
|
|
1
|
+
// plan-check reports the problems while the plan can still change, so nothing ambiguous reaches the finish.
|
|
21
2
|
|
|
22
|
-
import { SEPARATOR_ROW_RE,
|
|
23
|
-
|
|
24
|
-
export const REVOCATIONS_TITLE = 'UCs e ADRs que esta feature altera ou revoga';
|
|
3
|
+
import { SEPARATOR_ROW_RE, isTableLine, tableCells } from './gfm-table.js';
|
|
25
4
|
|
|
26
5
|
/** ADR numbers restart in every feature, so only `{feature}/ADR-{nnn}` is unambiguous. */
|
|
27
6
|
export const ADR_REF_RE = /^[a-z0-9][a-z0-9-]*\/ADR-\d+$/;
|
|
28
7
|
|
|
29
|
-
const HEADING_RE = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
|
|
30
8
|
const FENCE_RE = /^\s*(```|~~~)/;
|
|
31
|
-
const
|
|
32
|
-
/** A list item: the shape a revocation takes when the author skips the table. */
|
|
9
|
+
const REF_TOKEN_RE = /\b[a-z0-9][a-z0-9-]*\/ADR-\d+\b/;
|
|
33
10
|
const LIST_ITEM_RE = /^\s*(?:[-*+]|\d+[.)])\s/;
|
|
34
|
-
|
|
35
11
|
const CHANGES = new Map([['total', 'total'], ['parcial', 'parcial'], ['partial', 'parcial']]);
|
|
36
12
|
|
|
37
|
-
/** `—`, `–`, `-` and an empty cell all mean "none". */
|
|
38
13
|
const isNone = (cell) => /^(?:—|–|-)?$/.test(cell);
|
|
39
|
-
|
|
40
|
-
/** Id-like cells tolerate the typography an author reaches for: `code` and **bold**. */
|
|
41
14
|
const bare = (cell) => cell.replace(/^[`*]+|[`*]+$/g, '').trim();
|
|
42
15
|
|
|
43
|
-
/**
|
|
44
|
-
* The problems of one data row; `[]` = the row is applicable.
|
|
45
|
-
* @returns {{ entry: object|null, problems: Array<{ id: string|null, reason: string, message: string }> }}
|
|
46
|
-
*/
|
|
47
16
|
function readRow(line) {
|
|
48
17
|
const problems = [];
|
|
49
18
|
const problem = (id, reason, message) => problems.push({ id, reason, message });
|
|
50
19
|
|
|
51
20
|
const row = tableCells(line);
|
|
52
|
-
if (row.length <
|
|
53
|
-
problem(null, 'malformed-row', `linha com ${row.length} coluna(s), esperado
|
|
21
|
+
if (row.length < 3) {
|
|
22
|
+
problem(null, 'malformed-row', `linha com ${row.length} coluna(s), esperado 3 (ADR | Mudança | Escopo): ${line.trim()}`);
|
|
54
23
|
return { entry: null, problems };
|
|
55
24
|
}
|
|
56
25
|
const id = bare(row[0]);
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const scope = isNone(row[3]) ? null : row[3];
|
|
26
|
+
const change = CHANGES.get(bare(row[1]).toLowerCase());
|
|
27
|
+
const scope = isNone(row[2]) ? null : row[2];
|
|
60
28
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
problem(id || null, 'malformed-id', `"${id}" não é um UC-{nn} nem um {feature}/ADR-{nnn}`);
|
|
29
|
+
if (!ADR_REF_RE.test(id)) {
|
|
30
|
+
problem(id || null, 'malformed-id', `"${id}" não é um {feature}/ADR-{nnn}`);
|
|
64
31
|
return { entry: null, problems };
|
|
65
32
|
}
|
|
66
|
-
|
|
67
|
-
const change = CHANGES.get(changeCell);
|
|
68
33
|
if (!change) problem(id, 'malformed-change', `${id}: mudança "${bare(row[1])}" — use total ou parcial`);
|
|
69
|
-
|
|
70
|
-
if (kind === 'uc' && !substitute) {
|
|
71
|
-
problem(id, 'missing-substitute', `${id}: UC revogado precisa do substituto (UC-{nn}) — é o superseded_by do acervo`);
|
|
72
|
-
} else if (substitute && !(kind === 'uc' ? UC_ID_RE : ADR_REF_RE).test(substitute)) {
|
|
73
|
-
problem(id, 'malformed-substitute',
|
|
74
|
-
`${id}: substituto "${substitute}" — esperado ${kind === 'uc' ? 'UC-{nn}' : '{feature}/ADR-{nnn}'}`);
|
|
75
|
-
} else if (substitute === id) {
|
|
76
|
-
problem(id, 'self-substitute', `${id}: o substituto é o próprio ${id}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
34
|
if (change === 'parcial' && !scope) {
|
|
80
35
|
problem(id, 'missing-scope', `${id}: revogação parcial precisa dizer onde o antigo ainda vale (coluna Escopo)`);
|
|
81
36
|
} else if (change === 'total' && scope) {
|
|
82
37
|
problem(id, 'scope-on-total', `${id}: revogação total com escopo "${scope}" — se o antigo ainda vale em algum lugar, a mudança é parcial`);
|
|
83
38
|
}
|
|
84
39
|
|
|
85
|
-
return { entry: problems.length ? { id } : {
|
|
40
|
+
return { entry: problems.length ? { id } : { id, change, scope }, problems };
|
|
86
41
|
}
|
|
87
42
|
|
|
88
|
-
|
|
89
|
-
* @param {string} content - the whole `spec.md`
|
|
90
|
-
* @returns {{
|
|
91
|
-
* declared: boolean,
|
|
92
|
-
* entries: Array<{ kind: 'uc'|'adr', id: string, change: 'total'|'parcial', substitute: string|null, scope: string|null }>,
|
|
93
|
-
* problems: Array<{ id: string|null, reason: string, message: string }>
|
|
94
|
-
* }}
|
|
95
|
-
* `declared: false` — no section at all (a plan written before it existed).
|
|
96
|
-
*/
|
|
97
|
-
export function parseRevocations(content) {
|
|
98
|
-
const lines = String(content ?? '').split(/\r?\n/);
|
|
99
|
-
const title = REVOCATIONS_TITLE.toLowerCase();
|
|
100
|
-
|
|
101
|
-
let start = -1;
|
|
102
|
-
let level = 0;
|
|
103
|
-
for (let i = 0; i < lines.length; i++) {
|
|
104
|
-
const h = HEADING_RE.exec(lines[i]);
|
|
105
|
-
if (h && h[2].toLowerCase().startsWith(title)) { start = i + 1; level = h[1].length; break; }
|
|
106
|
-
}
|
|
107
|
-
if (start === -1) return { declared: false, entries: [], problems: [] };
|
|
108
|
-
|
|
109
|
-
let end = lines.length;
|
|
110
|
-
for (let i = start; i < lines.length; i++) {
|
|
111
|
-
const h = HEADING_RE.exec(lines[i]);
|
|
112
|
-
if (h && h[1].length <= level) { end = i; break; }
|
|
113
|
-
}
|
|
114
|
-
|
|
43
|
+
export function parseRevocations(conflicts) {
|
|
115
44
|
const problems = [];
|
|
116
|
-
const rows = [];
|
|
45
|
+
const rows = [];
|
|
117
46
|
let fenced = false;
|
|
118
|
-
let
|
|
119
|
-
let tableLines = 0;
|
|
47
|
+
let table = null;
|
|
120
48
|
|
|
121
|
-
for (const line of
|
|
122
|
-
if (FENCE_RE.test(line)) { fenced = !fenced; continue; }
|
|
49
|
+
for (const line of String(conflicts ?? '').split(/\r?\n/)) {
|
|
50
|
+
if (FENCE_RE.test(line)) { fenced = !fenced; table = null; continue; }
|
|
123
51
|
if (fenced) continue;
|
|
124
52
|
|
|
125
53
|
if (!isTableLine(line)) {
|
|
126
|
-
|
|
54
|
+
table = null;
|
|
55
|
+
const stray = LIST_ITEM_RE.test(line) ? REF_TOKEN_RE.exec(line) : null;
|
|
127
56
|
if (stray) {
|
|
128
57
|
problems.push({ id: stray[0], reason: 'outside-table',
|
|
129
|
-
message: `${stray[0]} aparece fora da tabela — só as linhas da tabela são aplicadas no finish` });
|
|
58
|
+
message: `${stray[0]} aparece fora da tabela ADR | Mudança | Escopo — só as linhas da tabela são aplicadas no finish` });
|
|
130
59
|
}
|
|
131
60
|
continue;
|
|
132
61
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
62
|
+
if (!table) {
|
|
63
|
+
table = { revocations: bare(tableCells(line)[0] ?? '').toLowerCase() === 'adr', separatorSeen: false };
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!table.revocations) continue;
|
|
67
|
+
if (!table.separatorSeen) {
|
|
68
|
+
if (SEPARATOR_ROW_RE.test(line)) { table.separatorSeen = true; continue; }
|
|
69
|
+
problems.push({ id: null, reason: 'unreadable-table',
|
|
70
|
+
message: 'tabela de revogação ilegível — falta a linha separadora (|---|---|---|) depois do cabeçalho; nenhuma linha foi lida' });
|
|
71
|
+
table.revocations = false;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
137
74
|
const { entry, problems: rowProblems } = readRow(line);
|
|
138
75
|
problems.push(...rowProblems);
|
|
139
76
|
if (entry) rows.push(entry);
|
|
140
77
|
}
|
|
141
78
|
|
|
142
|
-
if (tableLines > 0 && !separatorSeen) {
|
|
143
|
-
problems.push({ id: null, reason: 'unreadable-table',
|
|
144
|
-
message: 'tabela ilegível — falta a linha separadora (|---|---|---|---) depois do cabeçalho; nenhuma linha foi lida' });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// An id declared twice is ambiguous as a whole: none of its rows applies.
|
|
148
79
|
const count = new Map();
|
|
149
80
|
for (const r of rows) count.set(r.id, (count.get(r.id) ?? 0) + 1);
|
|
150
81
|
for (const [id, n] of count) {
|
|
151
82
|
if (n > 1) problems.push({ id, reason: 'duplicate-id', message: `${id} aparece em ${n} linhas — nenhuma foi aplicada; deixe uma só` });
|
|
152
83
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return { declared: true, entries, problems };
|
|
84
|
+
return { entries: rows.filter((r) => r.change && count.get(r.id) === 1), problems };
|
|
156
85
|
}
|