@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,32 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* A terceira camada do ciclo de reuso:
|
|
5
|
-
*
|
|
6
|
-
* PROSA (morph-plan, personas) ──► ARTEFATO (`reuse` em tasks.json)
|
|
7
|
-
* ──► COBRANÇA (este arquivo)
|
|
8
|
-
*
|
|
9
|
-
* Prosa sozinha não segura — `morph-brainstorming/SKILL.md:39` já manda olhar
|
|
10
|
-
* o que existe, e o ProspectPRO redefiniu `SimpleBuilding` em cinco arquivos da
|
|
11
|
-
* MESMA pasta assim mesmo: só a verificação em código segura o ciclo.
|
|
12
|
-
*
|
|
13
|
-
* Regras, na ordem em que reduzem ruído:
|
|
14
|
-
*
|
|
15
|
-
* 1. Só tipo (class/record/interface/enum/struct/type) — não função nem
|
|
16
|
-
* variável. É a categoria que o grafo casa com confiança.
|
|
17
|
-
* 2. Só MESMA LINGUAGEM. Contrato espelhado entre C# e TS (110 casos no
|
|
18
|
-
* ProspectPRO) é o escopo F da proposta: exige gerar tipos do contrato,
|
|
19
|
-
* não se resolve por aviso, e fica FORA daqui de propósito.
|
|
20
|
-
* 3. Mesma feature = achado (`error`); feature diferente = aviso
|
|
21
|
-
* (`warning`), porque VSA prescreve DTO homônimo entre slices.
|
|
22
|
-
* 4. Veredito `CRIAR` declarado no plano (tasks.json `reuse`) rebaixa o
|
|
23
|
-
* achado a aviso: a decisão foi tomada no Gate 2, com justificativa em
|
|
24
|
-
* `why`. O validador cobra a decisão AUSENTE, não a decisão tomada.
|
|
25
|
-
*
|
|
26
|
-
* Sem grafo o validador não roda (ADR-002: ausência de grafo nunca bloqueia).
|
|
27
|
-
*
|
|
28
|
-
* @module reuse-validator
|
|
29
|
-
*/
|
|
1
|
+
// Só tipos, só mesma linguagem (contrato espelhado C#↔TS não se resolve por aviso).
|
|
2
|
+
// Mesma feature = error; outra feature = warning (VSA admite DTO homônimo entre slices).
|
|
3
|
+
// Veredito CRIAR no plano rebaixa a warning: cobra-se a decisão AUSENTE, não a tomada.
|
|
30
4
|
|
|
31
5
|
import { existsSync, readFileSync } from 'fs';
|
|
32
6
|
import { execFileSync } from 'child_process';
|
|
@@ -34,34 +8,23 @@ import { join, isAbsolute } from 'path';
|
|
|
34
8
|
|
|
35
9
|
import { loadGraph } from '../../graph/graph-loader.js';
|
|
36
10
|
import { languageOf } from '../../graph/sanitize.js';
|
|
37
|
-
import { getAbsoluteOutputPath } from '../../../core/paths/output-schema.js';
|
|
38
11
|
import {
|
|
39
12
|
featureOf,
|
|
40
13
|
isIdiomaticLocalName,
|
|
41
14
|
isScannableFile,
|
|
42
15
|
scanDeclarations,
|
|
43
16
|
} from './symbol-scanner.js';
|
|
17
|
+
import { readPlanChecks } from '../../plan/checks.js';
|
|
44
18
|
|
|
45
|
-
/** Quantos locais citar por achado antes de resumir com "(+N)". */
|
|
46
19
|
const MAX_CITED = 4;
|
|
47
20
|
|
|
48
|
-
/**
|
|
49
|
-
* Índice "o que já existe": nome em minúsculas -> ocorrências reais do grafo.
|
|
50
|
-
*
|
|
51
|
-
* Só nós com `source_file` preenchido entram — nó com `source_file: ""` é stub
|
|
52
|
-
* não resolvido (BCL, NuGet, pacote externo), e apontar `Task` do .NET como
|
|
53
|
-
* duplicação seria ruído garantido. É o mesmo critério do `buildIndex`.
|
|
54
|
-
*
|
|
55
|
-
* @param {{nodes: object[]}} graph
|
|
56
|
-
* @returns {Map<string, Array<{name: string, file: string, language: string, feature: string}>>}
|
|
57
|
-
*/
|
|
58
21
|
export function buildExistingIndex(graph) {
|
|
59
22
|
const index = new Map();
|
|
60
23
|
|
|
61
24
|
for (const node of graph?.nodes ?? []) {
|
|
25
|
+
// `source_file` vazio é stub não resolvido (BCL, NuGet): `Task` do .NET não é duplicação.
|
|
62
26
|
if (!node.source_file) continue;
|
|
63
|
-
//
|
|
64
|
-
// carregam, e se distinguem pelo label com extensão.
|
|
27
|
+
// Nós de ARQUIVO também têm `_callable_class`; distinguem-se pelo label com extensão.
|
|
65
28
|
if (node._callable_class !== true) continue;
|
|
66
29
|
if (/\.[a-z0-9]+$/i.test(node.label)) continue;
|
|
67
30
|
if (!isScannableFile(node.source_file)) continue;
|
|
@@ -81,23 +44,10 @@ export function buildExistingIndex(graph) {
|
|
|
81
44
|
return index;
|
|
82
45
|
}
|
|
83
46
|
|
|
84
|
-
/**
|
|
85
|
-
* Casa as declarações desta feature contra o que já existe.
|
|
86
|
-
*
|
|
87
|
-
* Puro e sem I/O — é aqui que a regra mora, e é isto que o teste roda contra o
|
|
88
|
-
* grafo real do ProspectPRO.
|
|
89
|
-
*
|
|
90
|
-
* @param {Array<{name: string, kind: string, line: number, file: string}>} declarations
|
|
91
|
-
* @param {Map<string, Array<{name: string, file: string, language: string, feature: string}>>} existing
|
|
92
|
-
* @param {{verdicts?: Map<string, {verdict: string, target?: string}>}} [opts]
|
|
93
|
-
* @returns {Array<object>} issues, uma por símbolo duplicado
|
|
94
|
-
*/
|
|
95
47
|
export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
96
48
|
const verdicts = opts.verdicts ?? new Map();
|
|
97
49
|
const issues = [];
|
|
98
50
|
|
|
99
|
-
// Uma declaração por (nome, arquivo); o mesmo nome em N arquivos da feature
|
|
100
|
-
// vira UM achado com N locais, não N achados.
|
|
101
51
|
const byName = new Map();
|
|
102
52
|
for (const decl of declarations) {
|
|
103
53
|
const key = decl.name.toLowerCase();
|
|
@@ -112,17 +62,10 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
|
112
62
|
const declaredFiles = new Set(decls.map((d) => d.file));
|
|
113
63
|
const language = languageOf(decls[0].file);
|
|
114
64
|
|
|
115
|
-
//
|
|
116
|
-
// arquivos desta mesma leva (código novo que o grafo ainda não viu — ele
|
|
117
|
-
// só é refeito depois do commit, e é exatamente o caso SimpleBuilding).
|
|
65
|
+
// A leva também conta: o grafo só é refeito depois do commit e ainda não viu o código novo.
|
|
118
66
|
const fromGraph = (existing.get(key) ?? []).filter(
|
|
119
67
|
(e) => e.language === language && !declaredFiles.has(e.file),
|
|
120
68
|
);
|
|
121
|
-
// O filtro de linguagem vale AQUI TAMBÉM, não só contra o grafo: uma leva
|
|
122
|
-
// que toca `CatalogContracts.cs` e `catalog.types.ts` declara `CatalogItem`
|
|
123
|
-
// nos dois, na mesma feature `Catalog/` — e sem este filtro isso vira erro,
|
|
124
|
-
// que é precisamente o contrato espelhado C#↔TS que a proposta separa no
|
|
125
|
-
// escopo F por não se resolver com aviso. Medido no MORPH_GHLBrain.
|
|
126
69
|
const fromBatch = decls
|
|
127
70
|
.slice(1)
|
|
128
71
|
.filter((d) => languageOf(d.file) === language)
|
|
@@ -154,7 +97,7 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
|
154
97
|
? `Reuse ou estenda o tipo existente em vez de redeclarar. Confirme com ` +
|
|
155
98
|
`\`morph-spec graph explain ${decls[0].name}\` (sem grafo: ` +
|
|
156
99
|
`\`grep -rn "${decls[0].name}"\`) e, se a criação for mesmo necessária, ` +
|
|
157
|
-
'registre o veredito CRIAR com a justificativa em "why"
|
|
100
|
+
'registre o veredito CRIAR com a justificativa em "why" na tabela ## Reuso do plan.md.'
|
|
158
101
|
: `VSA tolera DTO homônimo entre slices — confirme que são conceitos ` +
|
|
159
102
|
`diferentes e não a mesma coisa escrita duas vezes.`,
|
|
160
103
|
});
|
|
@@ -166,67 +109,28 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
|
166
109
|
});
|
|
167
110
|
}
|
|
168
111
|
|
|
169
|
-
/**
|
|
170
|
-
* Vereditos de reuso carimbados no plano: `tasks[].reuse[]`.
|
|
171
|
-
*
|
|
172
|
-
* O veredito mora só no `tasks.json`: dado estruturado, lido aqui e no Gate 2.
|
|
173
|
-
*
|
|
174
|
-
* @param {string} projectPath
|
|
175
|
-
* @param {string} featureName
|
|
176
|
-
* @returns {Map<string, {verdict: string, target?: string}>}
|
|
177
|
-
*/
|
|
178
112
|
export function readReuseVerdicts(projectPath, featureName) {
|
|
179
113
|
const verdicts = new Map();
|
|
180
114
|
if (!featureName) return verdicts;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (!entry?.concept) continue;
|
|
189
|
-
verdicts.set(String(entry.concept).toLowerCase(), {
|
|
190
|
-
verdict: String(entry.verdict || '').toUpperCase(),
|
|
191
|
-
target: entry.target,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
} catch {
|
|
196
|
-
// Plano ilegível não é problema deste validador — o tasks-json-guard já
|
|
197
|
-
// cobra o schema, e aqui a ausência de veredito só torna a cobrança mais
|
|
198
|
-
// rígida, nunca mais frouxa.
|
|
115
|
+
// Sem extração, nenhum veredito: a cobrança só fica mais rígida, nunca mais frouxa.
|
|
116
|
+
for (const entry of readPlanChecks(projectPath, featureName).checks?.reuse ?? []) {
|
|
117
|
+
if (!entry?.concept) continue;
|
|
118
|
+
verdicts.set(String(entry.concept).toLowerCase(), {
|
|
119
|
+
verdict: String(entry.verdict || '').toUpperCase(),
|
|
120
|
+
target: entry.target,
|
|
121
|
+
});
|
|
199
122
|
}
|
|
200
123
|
return verdicts;
|
|
201
124
|
}
|
|
202
125
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
*
|
|
206
|
-
* Duas fontes, unidas, porque cada uma é cega no momento em que a outra
|
|
207
|
-
* enxerga: `outputs` do `tasks.json` é o contrato do framework (morph-implement
|
|
208
|
-
* §8 exige listar TODO arquivo produzido) e sobrevive ao commit; `git status`
|
|
209
|
-
* pega o que ainda não foi registrado numa task. Sem git, sem drama.
|
|
210
|
-
*
|
|
211
|
-
* @param {string} projectPath
|
|
212
|
-
* @param {string} featureName
|
|
213
|
-
* @returns {string[]} paths relativos ao projeto, com barra normal
|
|
214
|
-
*/
|
|
126
|
+
// União de duas fontes cegas em momentos diferentes: `arquivos:` das fatias sobrevivem
|
|
127
|
+
// ao commit; `git status` pega o que o plano não previu.
|
|
215
128
|
export function collectChangedFiles(projectPath, featureName) {
|
|
216
129
|
const files = new Set();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (existsSync(tasksPath)) {
|
|
221
|
-
const doc = JSON.parse(readFileSync(tasksPath, 'utf8').replace(/^/, ''));
|
|
222
|
-
for (const task of doc.tasks ?? []) {
|
|
223
|
-
for (const out of task.outputs ?? []) {
|
|
224
|
-
if (typeof out === 'string') files.add(out.replace(/\\/g, '/'));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
130
|
+
for (const slice of readPlanChecks(projectPath, featureName).checks?.slices ?? []) {
|
|
131
|
+
for (const file of slice.files ?? []) {
|
|
132
|
+
if (typeof file === 'string') files.add(file.replace(/\\/g, '/'));
|
|
227
133
|
}
|
|
228
|
-
} catch {
|
|
229
|
-
// tasks.json ausente/ilegível: sobra o git.
|
|
230
134
|
}
|
|
231
135
|
|
|
232
136
|
try {
|
|
@@ -238,32 +142,17 @@ export function collectChangedFiles(projectPath, featureName) {
|
|
|
238
142
|
});
|
|
239
143
|
for (const line of out.split(/\r?\n/)) {
|
|
240
144
|
if (!line.trim()) continue;
|
|
241
|
-
//
|
|
242
|
-
// Os paths vêm relativos à RAIZ do repositório — que no morph-spec é a
|
|
243
|
-
// própria árvore da feature (cada worktree é a sua raiz). Se algum dia
|
|
244
|
-
// não coincidir com `projectPath`, o `existsSync` adiante descarta o
|
|
245
|
-
// path: perde-se cobertura, nunca se acusa um arquivo que não existe.
|
|
145
|
+
// Rename vem como "XY antigo -> novo": só o destino interessa.
|
|
246
146
|
const path = line.slice(3).split(' -> ').pop().replace(/^"|"$/g, '');
|
|
247
147
|
if (path) files.add(path.replace(/\\/g, '/'));
|
|
248
148
|
}
|
|
249
149
|
} catch {
|
|
250
|
-
// Fora de um repo git, ou git indisponível: o
|
|
150
|
+
// Fora de um repo git, ou git indisponível: o plano já respondeu.
|
|
251
151
|
}
|
|
252
152
|
|
|
253
153
|
return [...files];
|
|
254
154
|
}
|
|
255
155
|
|
|
256
|
-
/**
|
|
257
|
-
* Validador plugado no `validation-runner` (e por ele no nó `validators` do
|
|
258
|
-
* `morph-spec verify`).
|
|
259
|
-
*
|
|
260
|
-
* @param {string} projectPath
|
|
261
|
-
* @param {string} featureName
|
|
262
|
-
* @param {object} [options]
|
|
263
|
-
* @param {string[]} [options.changedFiles] - override da coleta (testes)
|
|
264
|
-
* @param {object} [options._graph] - grafo já carregado (testes)
|
|
265
|
-
* @returns {Promise<{errors: number, warnings: number, issues: object[]}>}
|
|
266
|
-
*/
|
|
267
156
|
export async function validateReuse(projectPath, featureName = null, options = {}) {
|
|
268
157
|
const result = { errors: 0, warnings: 0, issues: [] };
|
|
269
158
|
|
|
@@ -273,11 +162,9 @@ export async function validateReuse(projectPath, featureName = null, options = {
|
|
|
273
162
|
})();
|
|
274
163
|
|
|
275
164
|
if (!graph) {
|
|
276
|
-
//
|
|
277
|
-
// segue em frente. Um validador que reprovasse aqui transformaria uma
|
|
278
|
-
// dependência opcional em pré-requisito.
|
|
165
|
+
// Sem grafo segue em frente: reprovar faria de uma dependência opcional um pré-requisito.
|
|
279
166
|
result._skipReason = 'sem grafo de codebase — `morph-spec graph build` habilita esta verificação';
|
|
280
|
-
// "
|
|
167
|
+
// "Não pude checar", diferente de "nada a checar": o verify relata em voz alta.
|
|
281
168
|
result._skipUnavailable = true;
|
|
282
169
|
return result;
|
|
283
170
|
}
|
|
@@ -1,74 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Por que não ler as declarações do grafo: o grafo só é reconstruído depois de
|
|
5
|
-
* um commit (o refresh em background de framework/hooks/shared/graph-refresh.js),
|
|
6
|
-
* então o código que acabou de ser escrito — o caso que este validador existe
|
|
7
|
-
* para pegar — ainda NÃO está nele. O grafo responde "o que já existia"; o
|
|
8
|
-
* disco responde "o que esta feature acabou de declarar". Inverter os dois
|
|
9
|
-
* papéis produziria um validador cego justamente na janela em que ele precisa
|
|
10
|
-
* morder — ver graph-loader.js `getFreshness`.
|
|
11
|
-
*
|
|
12
|
-
* A varredura é por linha, com regex ancorada no início da declaração: uma
|
|
13
|
-
* declaração citada dentro de uma string ou de um comentário de bloco pode
|
|
14
|
-
* passar. O custo de errar é um aviso a mais, nunca um bloqueio silencioso.
|
|
15
|
-
*
|
|
16
|
-
* @module reuse-symbol-scanner
|
|
17
|
-
*/
|
|
1
|
+
// Lê declarações do disco, não do grafo: o grafo só é refeito depois de um commit,
|
|
2
|
+
// então o código recém-escrito (o que este validador existe para pegar) ainda não está nele.
|
|
18
3
|
|
|
19
4
|
import { isTestPath, languageOf } from '../../graph/sanitize.js';
|
|
20
5
|
import { isProjectSource } from '../../../../framework/hooks/shared/source-files.js';
|
|
21
6
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* domínio. Todo componente React declara o seu `Props`; dois arquivos com
|
|
25
|
-
* `interface Props` não são o mesmo conceito escrito duas vezes — são dois
|
|
26
|
-
* conceitos diferentes com o nome que a convenção manda usar.
|
|
27
|
-
*
|
|
28
|
-
* A lista é curta de propósito e só admite nome genérico de UMA palavra: assim
|
|
29
|
-
* que há qualificador de domínio (`BuildingFiltersProps`, `FilterOption`) o
|
|
30
|
-
* nome volta a ser um conceito e a regra vale.
|
|
31
|
-
*/
|
|
7
|
+
// Tipo LOCAL de arquivo por convenção (todo componente tem seu `Props`), não conceito do
|
|
8
|
+
// domínio. Só nome genérico de UMA palavra: com qualificador (`FilterOption`) a regra vale.
|
|
32
9
|
const IDIOMATIC_LOCAL_NAMES = new Set([
|
|
33
10
|
'props', 'state', 'row', 'item', 'column', 'context', 'params', 'options',
|
|
34
11
|
]);
|
|
35
12
|
|
|
36
|
-
/** Declaração de tipo em C#. `partial` é capturado para ser excluído depois. */
|
|
37
13
|
const CSHARP_DECL =
|
|
38
14
|
/^\s*(?:\[[^\]]*\]\s*)*(?:(?:public|internal|private|protected|sealed|abstract|static|readonly|new|file|partial)\s+)*(class|record|interface|enum|struct)\s+([A-Za-z_]\w*)/;
|
|
39
15
|
|
|
40
|
-
/** Declaração de tipo em TS/JS. */
|
|
41
16
|
const WEB_DECL =
|
|
42
17
|
/^\s*(?:export\s+(?:default\s+)?)?(?:declare\s+)?(?:abstract\s+)?(interface|type|class|enum)\s+([A-Za-z_$][\w$]*)/;
|
|
43
18
|
|
|
44
|
-
/** Linha que é claramente comentário — não vale a pena parsear. */
|
|
45
19
|
const COMMENT_LINE = /^\s*(?:\/\/|\/\*|\*|<!--)/;
|
|
46
20
|
|
|
47
|
-
/**
|
|
48
|
-
* @param {string} sourceFile
|
|
49
|
-
* @returns {boolean} arquivo de teste, pelo mesmo predicado do grafo
|
|
50
|
-
*/
|
|
51
21
|
export function isTestFile(sourceFile) {
|
|
52
22
|
return isTestPath(String(sourceFile || '').replace(/\\/g, '/'));
|
|
53
23
|
}
|
|
54
24
|
|
|
55
|
-
/**
|
|
56
|
-
* @param {string} sourceFile
|
|
57
|
-
* @returns {boolean} o arquivo entra na varredura de reuso
|
|
58
|
-
*/
|
|
59
25
|
export function isScannableFile(sourceFile) {
|
|
60
26
|
return isProjectSource(sourceFile) && !isTestFile(sourceFile);
|
|
61
27
|
}
|
|
62
28
|
|
|
63
|
-
/**
|
|
64
|
-
* "Feature" de um path, com a mesma heurística do baseline
|
|
65
|
-
* (docs/specs/code-reuse-gap/measure-duplication.mjs): o segmento após
|
|
66
|
-
* `features/`/`Features/` quando existe, senão a pasta do arquivo. É o que
|
|
67
|
-
* separa o achado indefensável (mesma feature) do aviso tolerado por VSA.
|
|
68
|
-
*
|
|
69
|
-
* @param {string} sourceFile
|
|
70
|
-
* @returns {string}
|
|
71
|
-
*/
|
|
29
|
+
/** Segmento após `features/`/`Features/`, senão a pasta do arquivo. */
|
|
72
30
|
export function featureOf(sourceFile) {
|
|
73
31
|
const f = String(sourceFile || '').replace(/\\/g, '/');
|
|
74
32
|
const m = /(?:^|\/)(?:features|Features)\/([^/]+)/.exec(f);
|
|
@@ -76,25 +34,11 @@ export function featureOf(sourceFile) {
|
|
|
76
34
|
return f.split('/').slice(0, -1).join('/').toLowerCase();
|
|
77
35
|
}
|
|
78
36
|
|
|
79
|
-
/**
|
|
80
|
-
* @param {string} name
|
|
81
|
-
* @returns {boolean} nome genérico que a convenção usa como tipo local
|
|
82
|
-
*/
|
|
83
37
|
export function isIdiomaticLocalName(name) {
|
|
84
38
|
return IDIOMATIC_LOCAL_NAMES.has(String(name).toLowerCase());
|
|
85
39
|
}
|
|
86
40
|
|
|
87
|
-
|
|
88
|
-
* Declarações de tipo de um arquivo.
|
|
89
|
-
*
|
|
90
|
-
* `partial` (C#) é reconhecido e DESCARTADO: declarar a mesma classe em dois
|
|
91
|
-
* arquivos é o propósito da palavra-chave, e sinalizá-la seria um falso
|
|
92
|
-
* positivo garantido em qualquer projeto .NET com código gerado.
|
|
93
|
-
*
|
|
94
|
-
* @param {string} content - conteúdo do arquivo
|
|
95
|
-
* @param {string} sourceFile - path, usado só para decidir a linguagem
|
|
96
|
-
* @returns {Array<{name: string, kind: string, line: number}>}
|
|
97
|
-
*/
|
|
41
|
+
// `partial` (C#) é descartado: declarar a mesma classe em dois arquivos é o propósito da palavra-chave.
|
|
98
42
|
export function scanDeclarations(content, sourceFile) {
|
|
99
43
|
const language = languageOf(sourceFile);
|
|
100
44
|
const re = language === 'dotnet' ? CSHARP_DECL : WEB_DECL;
|
|
@@ -1,89 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* cited in recap.md's "**Files:**" bullet lists have an owning task in
|
|
4
|
-
* tasks.json's `outputs`?
|
|
5
|
-
*
|
|
6
|
-
* This is the DETERMINISTIC half of a two-layer fix. The NLH half lives in
|
|
7
|
-
* `framework/skills/core/morph-implement/SKILL.md` §7 ("outputs DEVE listar
|
|
8
|
-
* TODO arquivo criado ou modificado pela task") and targets the root cause: a
|
|
9
|
-
* file shipped without EVER being declared anywhere (not in outputs, not in
|
|
10
|
-
* recap.md — the real-world case that motivated this module, see the finding
|
|
11
|
-
* below). This module cannot catch THAT class of drift — a file absent from
|
|
12
|
-
* both documents is invisible to a check that only compares the two documents
|
|
13
|
-
* against each other. What it DOES catch is a narrower, complementary class:
|
|
14
|
-
* recap.md and tasks.json DISAGREEING — an author cited a file in the recap's
|
|
15
|
-
* prose but forgot (or a later task re-touched it without) declaring it in any
|
|
16
|
-
* task's `outputs`. That disagreement is real signal even though it is not the
|
|
17
|
-
* same defect the SKILL.md rule targets.
|
|
18
|
-
*
|
|
19
|
-
* Motivating evidence: run 2026-08-12T133732Z-vet-visit-followup-reminder —
|
|
20
|
-
* `ApplicationDbContextFactory.cs` shipped in production with no owning task
|
|
21
|
-
* in `outputs` AND no mention in recap.md (out of this module's reach by
|
|
22
|
-
* construction). Separately, `src/VetClinic.Api/appsettings.json` and
|
|
23
|
-
* `Properties/launchSettings.json` WERE cited in T1's recap `**Files:**` list
|
|
24
|
-
* but never appear in any task's `outputs` — exactly the disagreement this
|
|
25
|
-
* module flags.
|
|
26
|
-
*
|
|
27
|
-
* Advisory, never blocking: the returned `status` is only ever 'pass' | 'warn'
|
|
28
|
-
* | 'skip' — never 'fail'. `runVerify()` keeps this result OUTSIDE `nodes` on
|
|
29
|
-
* purpose so it can never flip the overall build/tests/validators/e2e status
|
|
30
|
-
* computed from `nodes` alone (see verify-runner.js).
|
|
31
|
-
*
|
|
32
|
-
* Fail-open throughout: no recap.md, no tasks.json, or a parse error all
|
|
33
|
-
* degrade to `{ status: 'skip' }` rather than throwing or warning about the
|
|
34
|
-
* absence itself — this is rastreabilidade tooling, not a completeness gate.
|
|
35
|
-
*
|
|
36
|
-
* @module traceability-check
|
|
37
|
-
*/
|
|
1
|
+
// Advisory: status is only 'pass' | 'warn' | 'skip', and runVerify() keeps it outside
|
|
2
|
+
// `nodes` so it never flips the verify rollup.
|
|
38
3
|
|
|
39
4
|
import { existsSync, readFileSync } from 'fs';
|
|
40
|
-
import {
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { readPlanChecks } from '../plan/checks.js';
|
|
7
|
+
import { sliceProgress, sliceReportPath } from '../../../framework/hooks/shared/slice-progress.js';
|
|
41
8
|
|
|
42
|
-
|
|
43
|
-
const FILES_BLOCK_RE = /\*\*Files:\*\*[ \t]*\r?\n((?:^[ \t]*-[ \t].*\r?\n?)+)/gm;
|
|
9
|
+
const FILES_BLOCK_RE = /\*\*Arquivos:\*\*([^\r\n]*)\r?\n?((?:^[ \t]*-[ \t].*\r?\n?)*)/gm;
|
|
44
10
|
|
|
45
|
-
/** Backtick-quoted spans within a bullet line. */
|
|
46
11
|
const BACKTICK_RE = /`([^`]+)`/g;
|
|
47
12
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* language shorthand real recaps use:
|
|
52
|
-
*
|
|
53
|
-
* - A line with exactly one backtick span: that span is a candidate,
|
|
54
|
-
* whether or not it contains a `/` (a legitimate top-level file, e.g.
|
|
55
|
-
* `` `VetClinic.sln` ``, has no slash).
|
|
56
|
-
* - A line with multiple comma-separated backtick spans (e.g.
|
|
57
|
-
* `` `a/b.json`, `c.json`, `d/e.json` ``): only the FIRST span and any
|
|
58
|
-
* LATER span that itself contains `/` are candidates. A later span with
|
|
59
|
-
* no `/` is ambiguous shorthand (a bare filename implicitly relative to
|
|
60
|
-
* the first span's directory, or a suffix like `` `.Designer.cs` ``) and
|
|
61
|
-
* is skipped rather than guessed — false negatives are the acceptable
|
|
62
|
-
* failure mode for advisory tooling, false positives are not.
|
|
63
|
-
* - Any span containing `{`/`}` (brace-expansion shorthand, e.g.
|
|
64
|
-
* `` `Foo/{A,B,C}.cs` ``) is skipped entirely — decomposing it correctly
|
|
65
|
-
* would require guessing at delimiters this format doesn't standardize.
|
|
66
|
-
* - Any span ending in `/` is a directory reference, not a file — skipped.
|
|
67
|
-
*
|
|
68
|
-
* @param {string} recapText
|
|
69
|
-
* @returns {string[]} de-duplicated, normalized (forward-slash) candidate paths
|
|
70
|
-
*/
|
|
71
|
-
export function extractCitedFiles(recapText) {
|
|
13
|
+
// A later span without `/` is ambiguous shorthand and is skipped, not guessed: false
|
|
14
|
+
// negatives are acceptable for advisory tooling, false positives are not.
|
|
15
|
+
export function extractCitedFiles(reportText) {
|
|
72
16
|
const cited = new Set();
|
|
73
|
-
if (!
|
|
17
|
+
if (!reportText) return [];
|
|
74
18
|
|
|
75
19
|
FILES_BLOCK_RE.lastIndex = 0;
|
|
76
20
|
let blockMatch;
|
|
77
|
-
while ((blockMatch = FILES_BLOCK_RE.exec(
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
for (const line of lines) {
|
|
21
|
+
while ((blockMatch = FILES_BLOCK_RE.exec(reportText)) !== null) {
|
|
22
|
+
const bullets = blockMatch[2].split(/\r?\n/).filter((l) => /^[ \t]*-[ \t]/.test(l));
|
|
23
|
+
for (const line of [blockMatch[1], ...bullets]) {
|
|
82
24
|
const spans = [...line.matchAll(BACKTICK_RE)].map((m) => m[1].trim()).filter(Boolean);
|
|
83
25
|
spans.forEach((span, idx) => {
|
|
84
|
-
if (idx > 0 && !span.includes('/')) return;
|
|
85
|
-
if (span.includes('{') || span.includes('}')) return;
|
|
86
|
-
if (span.endsWith('/')) return;
|
|
26
|
+
if (idx > 0 && !span.includes('/')) return;
|
|
27
|
+
if (span.includes('{') || span.includes('}')) return;
|
|
28
|
+
if (span.endsWith('/')) return;
|
|
87
29
|
cited.add(normalizePath(span));
|
|
88
30
|
});
|
|
89
31
|
}
|
|
@@ -92,114 +34,70 @@ export function extractCitedFiles(recapText) {
|
|
|
92
34
|
return [...cited];
|
|
93
35
|
}
|
|
94
36
|
|
|
95
|
-
/**
|
|
96
|
-
* Backslashes to forward slashes, strip a leading `./`.
|
|
97
|
-
*
|
|
98
|
-
* Exported because `src/lib/tasks/test-scope.js` normalises the SAME thing —
|
|
99
|
-
* a task's `outputs` entry — before deciding which tests a task-scoped verify
|
|
100
|
-
* runs. One normaliser for `outputs`, so the traceability check and the test
|
|
101
|
-
* recorte never disagree about which file a task delivered.
|
|
102
|
-
*/
|
|
103
37
|
export function normalizePath(p) {
|
|
104
38
|
return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
105
39
|
}
|
|
106
40
|
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*/
|
|
115
|
-
export function collectDoneOutputs(projectPath, featureName) {
|
|
116
|
-
let tasksPath;
|
|
117
|
-
try {
|
|
118
|
-
tasksPath = getAbsoluteOutputPath(projectPath, featureName, 'tasks');
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
if (!existsSync(tasksPath)) return null;
|
|
123
|
-
|
|
124
|
-
let data;
|
|
125
|
-
try {
|
|
126
|
-
data = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
127
|
-
} catch {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const tasks = Array.isArray(data?.tasks) ? data.tasks : [];
|
|
132
|
-
const outputs = [];
|
|
133
|
-
for (const task of tasks) {
|
|
134
|
-
if (task?.status !== 'done') continue;
|
|
135
|
-
for (const output of task.outputs || []) {
|
|
136
|
-
if (typeof output === 'string' && output.trim()) outputs.push(normalizePath(output));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return outputs;
|
|
41
|
+
/** null when the plan has no readable extraction. */
|
|
42
|
+
export function collectSliceFiles(projectPath, featureName) {
|
|
43
|
+
const checks = readPlanChecks(projectPath, featureName).checks;
|
|
44
|
+
if (!checks) return null;
|
|
45
|
+
return checks.slices.flatMap((slice) => (slice.files ?? [])
|
|
46
|
+
.filter((f) => typeof f === 'string' && f.trim())
|
|
47
|
+
.map(normalizePath));
|
|
140
48
|
}
|
|
141
49
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
* @param {string} file
|
|
147
|
-
* @param {string[]} outputs
|
|
148
|
-
* @returns {boolean}
|
|
149
|
-
*/
|
|
150
|
-
function isCovered(file, outputs) {
|
|
151
|
-
for (const output of outputs) {
|
|
152
|
-
if (output === file) return true;
|
|
153
|
-
if (output.endsWith('/') && file.startsWith(output)) return true;
|
|
50
|
+
function isCovered(file, declared) {
|
|
51
|
+
for (const entry of declared) {
|
|
52
|
+
if (entry === file) return true;
|
|
53
|
+
if (entry.endsWith('/') && file.startsWith(entry)) return true;
|
|
154
54
|
}
|
|
155
55
|
return false;
|
|
156
56
|
}
|
|
157
57
|
|
|
158
|
-
|
|
159
|
-
* Run the recap-vs-outputs traceability check for a feature.
|
|
160
|
-
*
|
|
161
|
-
* @param {string} projectPath
|
|
162
|
-
* @param {string} featureName
|
|
163
|
-
* @returns {{ status: 'pass'|'warn'|'skip', warnings: string[], reason: string|null, citedCount: number }}
|
|
164
|
-
*/
|
|
165
|
-
export function checkOutputsTraceability(projectPath, featureName) {
|
|
166
|
-
let recapPath;
|
|
58
|
+
function readOrNull(path) {
|
|
167
59
|
try {
|
|
168
|
-
|
|
60
|
+
return existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
169
61
|
} catch {
|
|
170
|
-
return
|
|
62
|
+
return null;
|
|
171
63
|
}
|
|
64
|
+
}
|
|
172
65
|
|
|
173
|
-
|
|
174
|
-
|
|
66
|
+
export function checkOutputsTraceability(projectPath, featureName) {
|
|
67
|
+
const featureDir = join(projectPath, '.morph', 'features', featureName);
|
|
68
|
+
const progress = sliceProgress(featureDir);
|
|
69
|
+
const reports = (progress?.slices ?? [])
|
|
70
|
+
.map((slice) => ({ id: slice.id, text: readOrNull(sliceReportPath(featureDir, slice.id)) }))
|
|
71
|
+
.filter((r) => r.text !== null);
|
|
72
|
+
if (reports.length === 0) {
|
|
73
|
+
return { status: 'skip', warnings: [], reason: 'no slice reports', citedCount: 0 };
|
|
175
74
|
}
|
|
176
75
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
76
|
+
const citedBy = new Map();
|
|
77
|
+
for (const { id, text } of reports) {
|
|
78
|
+
for (const file of extractCitedFiles(text)) {
|
|
79
|
+
if (!citedBy.has(file)) citedBy.set(file, []);
|
|
80
|
+
citedBy.get(file).push(id);
|
|
81
|
+
}
|
|
182
82
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (cited.length === 0) {
|
|
186
|
-
return { status: 'skip', warnings: [], reason: 'no Files: entries found in recap.md', citedCount: 0 };
|
|
83
|
+
if (citedBy.size === 0) {
|
|
84
|
+
return { status: 'skip', warnings: [], reason: 'no Arquivos: entries found in the slice reports', citedCount: 0 };
|
|
187
85
|
}
|
|
188
86
|
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
return { status: 'skip', warnings: [], reason: 'no
|
|
87
|
+
const declared = collectSliceFiles(projectPath, featureName);
|
|
88
|
+
if (declared === null) {
|
|
89
|
+
return { status: 'skip', warnings: [], reason: 'no checks.json', citedCount: citedBy.size };
|
|
192
90
|
}
|
|
193
91
|
|
|
194
|
-
const warnings =
|
|
195
|
-
.filter((file) => !isCovered(file,
|
|
92
|
+
const warnings = [...citedBy.keys()]
|
|
93
|
+
.filter((file) => !isCovered(file, declared))
|
|
196
94
|
.sort()
|
|
197
|
-
.map((file) => `${file} — cited in
|
|
95
|
+
.map((file) => `${file} — cited in reports/${citedBy.get(file).join(', ')} but declared in no slice's arquivos:`);
|
|
198
96
|
|
|
199
97
|
return {
|
|
200
98
|
status: warnings.length > 0 ? 'warn' : 'pass',
|
|
201
99
|
warnings,
|
|
202
100
|
reason: null,
|
|
203
|
-
citedCount:
|
|
101
|
+
citedCount: citedBy.size,
|
|
204
102
|
};
|
|
205
103
|
}
|