@polymorphism-tech/morph-spec 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +4 -62
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +11 -67
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +34 -12
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +14 -96
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +28 -275
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -2,25 +2,8 @@ import { glob } from 'glob';
|
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Next.js Component Validator
|
|
7
|
-
*
|
|
8
|
-
* Validates .tsx files for:
|
|
9
|
-
* 1. Unnecessary 'use client' directives (no interactivity)
|
|
10
|
-
* 2. Missing 'use client' when React hooks are used
|
|
11
|
-
* 3. File naming convention (kebab-case)
|
|
12
|
-
*
|
|
13
|
-
* @module next-component-validator
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
// ============================================
|
|
17
|
-
// CONSTANTS
|
|
18
|
-
// ============================================
|
|
19
|
-
|
|
20
|
-
/** Hooks/patterns that require 'use client' */
|
|
21
5
|
const CLIENT_HOOKS = ['useState', 'useEffect', 'useRef', 'useReducer', 'useCallback', 'useMemo', 'useContext'];
|
|
22
6
|
|
|
23
|
-
/** Event handler patterns that indicate interactivity (hooks are checked via CLIENT_HOOKS) */
|
|
24
7
|
const EVENT_HANDLER_PATTERNS = [
|
|
25
8
|
/\bonClick\b/,
|
|
26
9
|
/\bonChange\b/,
|
|
@@ -28,66 +11,29 @@ const EVENT_HANDLER_PATTERNS = [
|
|
|
28
11
|
/\bonFocus\b/,
|
|
29
12
|
/\bonBlur\b/,
|
|
30
13
|
/\bonKeyDown\b/,
|
|
31
|
-
// Qualquer outro handler de evento em JSX (onMouseEnter, onKeyUp, onScroll, onDrop…).
|
|
32
|
-
// Sem isto, a lista fechada acima transforma interatividade legítima em falso-positivo.
|
|
33
14
|
/\bon[A-Z]\w*\s*=\s*\{/,
|
|
34
15
|
];
|
|
35
16
|
|
|
36
|
-
|
|
37
|
-
* Chamada de QUALQUER hook, não só os sete nativos: `useRouter()`, `useQuery()`, `useForm<T>()`.
|
|
38
|
-
* O `[<(]` é o que faz `useState<Record<string, string>>(` contar — um hook com argumento de
|
|
39
|
-
* tipo não casa com `useState\s*\(` e passava despercebido.
|
|
40
|
-
*/
|
|
17
|
+
// `[<(]` faz hook com argumento de tipo (`useState<Record<string, string>>(`) contar.
|
|
41
18
|
const HOOK_CALL_PATTERN = /\buse[A-Z]\w*\s*[<(]/;
|
|
42
19
|
|
|
43
|
-
|
|
44
|
-
* APIs de React que só existem no cliente. `forwardRef` e `createContext` lançam em Server
|
|
45
|
-
* Component, então um módulo que os usa PRECISA da diretiva, mesmo sem hook nem handler.
|
|
46
|
-
*/
|
|
20
|
+
// Lançam em Server Component: o módulo precisa da diretiva mesmo sem hook nem handler.
|
|
47
21
|
const CLIENT_ONLY_APIS = [/\bforwardRef\b/, /\bcreateContext\b/];
|
|
48
22
|
|
|
49
|
-
/** Globais de browser: quem os toca não roda no servidor. */
|
|
50
23
|
const BROWSER_GLOBALS = /\b(window|document|localStorage|sessionStorage|navigator)\s*[.[]/;
|
|
51
24
|
|
|
52
|
-
|
|
53
|
-
* Pacotes cujos componentes são client-only. O caso que mais gerava ruído é o wrapper de
|
|
54
|
-
* shadcn/Radix: ele só reexporta primitivas, então não tem hook nem handler no próprio
|
|
55
|
-
* arquivo — e era acusado para sempre, apesar de estar correto.
|
|
56
|
-
*/
|
|
25
|
+
// Wrapper shadcn/Radix só reexporta primitivas client-only, sem hook nem handler no arquivo.
|
|
57
26
|
const CLIENT_ONLY_IMPORTS = /from\s+['"](@radix-ui\/|sonner|@react-pdf\/|file-saver|framer-motion|motion\/|recharts|next\/navigation|react-hook-form|@tanstack\/react-query|embla-carousel|cmdk|vaul|react-day-picker|input-otp|@dnd-kit\/)/;
|
|
58
27
|
|
|
59
|
-
/** Next.js special files exempt from naming checks */
|
|
60
28
|
const NEXTJS_SPECIAL_FILES = ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx', 'not-found.tsx', 'route.ts', 'middleware.ts'];
|
|
61
29
|
|
|
62
|
-
// ============================================
|
|
63
|
-
// MAIN VALIDATION FUNCTION
|
|
64
|
-
// ============================================
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @typedef {Object} ValidationIssue
|
|
68
|
-
* @property {'error' | 'warning' | 'info'} type
|
|
69
|
-
* @property {string} message
|
|
70
|
-
* @property {string} [suggestion]
|
|
71
|
-
* @property {string} file
|
|
72
|
-
* @property {number} [line]
|
|
73
|
-
*/
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validates a Next.js component file.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} content - File content
|
|
79
|
-
* @param {string} filePath - Relative file path
|
|
80
|
-
* @returns {ValidationIssue[]}
|
|
81
|
-
*/
|
|
82
30
|
export function validateNextComponent(content, filePath) {
|
|
83
31
|
const issues = [];
|
|
84
32
|
|
|
85
|
-
// Only validate .tsx and .ts files
|
|
86
33
|
if (!filePath.endsWith('.tsx') && !filePath.endsWith('.ts')) {
|
|
87
34
|
return issues;
|
|
88
35
|
}
|
|
89
36
|
|
|
90
|
-
// Skip non-component utility files (no JSX, no hooks)
|
|
91
37
|
const hasJsx = /(?<![a-zA-Z0-9])<[A-Z][a-zA-Z]*[\s/>]|(?<![a-zA-Z0-9])<[a-z][a-z]+[\s>/]/.test(content);
|
|
92
38
|
const hasHooks = CLIENT_HOOKS.some(hook => content.includes(hook));
|
|
93
39
|
|
|
@@ -97,13 +43,10 @@ export function validateNextComponent(content, filePath) {
|
|
|
97
43
|
|
|
98
44
|
const hasUseClient = /^'use client';?|^"use client";?/.test(content.trimStart());
|
|
99
45
|
|
|
100
|
-
// Check 1: use client present but no interactivity
|
|
101
46
|
if (hasUseClient) {
|
|
102
47
|
const hasEventHandlers = EVENT_HANDLER_PATTERNS.some(pattern => pattern.test(content));
|
|
103
48
|
const hasClientHooks = CLIENT_HOOKS.some(hook => new RegExp(`\\b${hook}\\b`).test(content));
|
|
104
|
-
//
|
|
105
|
-
// pede a remoção de uma diretiva que o build precisa, então a checagem erra para o lado
|
|
106
|
-
// de ficar calada.
|
|
49
|
+
// Falso-positivo pediria remover uma diretiva que o build precisa: erra para o lado calado.
|
|
107
50
|
const needsClient =
|
|
108
51
|
hasEventHandlers ||
|
|
109
52
|
hasClientHooks ||
|
|
@@ -121,7 +64,6 @@ export function validateNextComponent(content, filePath) {
|
|
|
121
64
|
}
|
|
122
65
|
}
|
|
123
66
|
|
|
124
|
-
// Check 2: hooks used without use client
|
|
125
67
|
// .ts files (custom hooks, utilities) never render JSX — skip this check
|
|
126
68
|
if (!hasUseClient) {
|
|
127
69
|
if (!filePath.endsWith('.tsx')) return issues;
|
|
@@ -137,7 +79,6 @@ export function validateNextComponent(content, filePath) {
|
|
|
137
79
|
}
|
|
138
80
|
}
|
|
139
81
|
|
|
140
|
-
// Check 3: file naming convention (kebab-case)
|
|
141
82
|
const fileName = filePath.split('/').pop() ?? '';
|
|
142
83
|
const isSpecialFile = NEXTJS_SPECIAL_FILES.includes(fileName);
|
|
143
84
|
|
|
@@ -157,10 +98,6 @@ export function validateNextComponent(content, filePath) {
|
|
|
157
98
|
return issues;
|
|
158
99
|
}
|
|
159
100
|
|
|
160
|
-
// ============================================
|
|
161
|
-
// HELPERS
|
|
162
|
-
// ============================================
|
|
163
|
-
|
|
164
101
|
function toKebabCase(str) {
|
|
165
102
|
return str
|
|
166
103
|
.replace(/([A-Z])/g, '-$1')
|
|
@@ -168,22 +105,9 @@ function toKebabCase(str) {
|
|
|
168
105
|
.replace(/^-/, '');
|
|
169
106
|
}
|
|
170
107
|
|
|
171
|
-
// ============================================
|
|
172
|
-
// PROJECT-LEVEL SCANNER
|
|
173
|
-
// ============================================
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Validates all Next.js component files in a project directory.
|
|
177
|
-
* Wraps validateNextComponent for use in validation-runner.js.
|
|
178
|
-
*
|
|
179
|
-
* @param {string} projectPath - Root path of the project
|
|
180
|
-
* @param {Object} _options - Validator options (unused, for interface compatibility)
|
|
181
|
-
* @returns {Promise<{ errors: number, warnings: number, issues: Array }>}
|
|
182
|
-
*/
|
|
183
108
|
export async function validateNextComponentFiles(projectPath, _options = {}) {
|
|
184
109
|
const result = { errors: 0, warnings: 0, issues: [] };
|
|
185
110
|
|
|
186
|
-
// Find all .tsx and .ts files under src/
|
|
187
111
|
const srcDir = join(projectPath, 'src');
|
|
188
112
|
const normalizedSrc = srcDir.replace(/\\/g, '/');
|
|
189
113
|
const pattern = normalizedSrc + '/**/*.{tsx,ts}';
|
|
@@ -197,7 +121,6 @@ export async function validateNextComponentFiles(projectPath, _options = {}) {
|
|
|
197
121
|
continue;
|
|
198
122
|
}
|
|
199
123
|
|
|
200
|
-
// Make path relative for cleaner output
|
|
201
124
|
const normFilePath = filePath.replace(/\\/g, '/');
|
|
202
125
|
const relPath = normFilePath.replace(projectPath.replace(/\\/g, '/') + '/', '');
|
|
203
126
|
const fileIssues = validateNextComponent(content, relPath);
|
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package Compatibility Validator
|
|
3
|
-
*
|
|
4
|
-
* Validates NuGet package versions against .NET compatibility matrix.
|
|
5
|
-
* Critical for .NET 10 migration - prevents runtime errors.
|
|
6
|
-
*
|
|
7
|
-
* MORPH-SPEC 3.0 - Sprint 4
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { readFileSync } from 'fs';
|
|
11
2
|
import { join } from 'path';
|
|
12
3
|
import { glob } from 'glob';
|
|
13
4
|
import chalk from 'chalk';
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
* Compatibility matrix for .NET versions
|
|
17
|
-
* Source: framework/standards/dotnet10-compatibility.md
|
|
18
|
-
*/
|
|
6
|
+
// Source: framework/standards/dotnet10-compatibility.md
|
|
19
7
|
const COMPATIBILITY_MATRIX = {
|
|
20
8
|
'Hangfire.AspNetCore': {
|
|
21
9
|
9: '1.8.0',
|
|
@@ -50,7 +38,7 @@ const COMPATIBILITY_MATRIX = {
|
|
|
50
38
|
breaking: false
|
|
51
39
|
},
|
|
52
40
|
'Microsoft.SemanticKernel': {
|
|
53
|
-
9: null,
|
|
41
|
+
9: null,
|
|
54
42
|
10: null,
|
|
55
43
|
deprecated: true,
|
|
56
44
|
replacement: 'Microsoft.Extensions.AI.Abstractions',
|
|
@@ -58,17 +46,11 @@ const COMPATIBILITY_MATRIX = {
|
|
|
58
46
|
}
|
|
59
47
|
};
|
|
60
48
|
|
|
61
|
-
/**
|
|
62
|
-
* Package Validator Class
|
|
63
|
-
*/
|
|
64
49
|
export class PackageValidator {
|
|
65
50
|
constructor(projectPath = '.') {
|
|
66
51
|
this.projectPath = projectPath;
|
|
67
52
|
}
|
|
68
53
|
|
|
69
|
-
/**
|
|
70
|
-
* Validate all .csproj files in project
|
|
71
|
-
*/
|
|
72
54
|
async validateAll() {
|
|
73
55
|
const csprojFiles = await glob('**/*.csproj', {
|
|
74
56
|
cwd: this.projectPath,
|
|
@@ -86,10 +68,7 @@ export class PackageValidator {
|
|
|
86
68
|
const allIssues = [];
|
|
87
69
|
|
|
88
70
|
for (const file of csprojFiles) {
|
|
89
|
-
// glob
|
|
90
|
-
// resolve against projectPath — reading them relative to process.cwd()
|
|
91
|
-
// raised ENOENT whenever the two differed (worktree, fleet, a verify
|
|
92
|
-
// launched from the primary root), and the throw read as a clean pass.
|
|
71
|
+
// glob paths are relative to `cwd: this.projectPath`, never to process.cwd().
|
|
93
72
|
const result = await this.validateFile(join(this.projectPath, file));
|
|
94
73
|
if (result.issues.length > 0) {
|
|
95
74
|
results.push({ file, ...result });
|
|
@@ -101,9 +80,7 @@ export class PackageValidator {
|
|
|
101
80
|
status: results.length === 0 ? 'ok' : 'error',
|
|
102
81
|
totalFiles: csprojFiles.length,
|
|
103
82
|
filesWithIssues: results.length,
|
|
104
|
-
// The runner's contract
|
|
105
|
-
// read as `errors: 0` and the run reported "✅ All validations passed"
|
|
106
|
-
// while holding a list of NuGet packages that do not build on .NET 10.
|
|
83
|
+
// The runner's contract: without these counts the findings read as `errors: 0`.
|
|
107
84
|
errors: allIssues.filter(i => i.level === 'error').length,
|
|
108
85
|
warnings: allIssues.filter(i => i.level === 'warning').length,
|
|
109
86
|
issues: allIssues,
|
|
@@ -111,13 +88,9 @@ export class PackageValidator {
|
|
|
111
88
|
};
|
|
112
89
|
}
|
|
113
90
|
|
|
114
|
-
/**
|
|
115
|
-
* Validate single .csproj file
|
|
116
|
-
*/
|
|
117
91
|
async validateFile(filePath) {
|
|
118
92
|
const content = readFileSync(filePath, 'utf-8');
|
|
119
93
|
|
|
120
|
-
// Extract .NET version
|
|
121
94
|
const tfmMatch = content.match(/<TargetFramework>(.*?)<\/TargetFramework>/);
|
|
122
95
|
if (!tfmMatch) {
|
|
123
96
|
return {
|
|
@@ -142,10 +115,8 @@ export class PackageValidator {
|
|
|
142
115
|
};
|
|
143
116
|
}
|
|
144
117
|
|
|
145
|
-
// Extract package references
|
|
146
118
|
const packages = this.extractPackages(content);
|
|
147
119
|
|
|
148
|
-
// Validate each package
|
|
149
120
|
const issues = [];
|
|
150
121
|
|
|
151
122
|
for (const pkg of packages) {
|
|
@@ -162,9 +133,6 @@ export class PackageValidator {
|
|
|
162
133
|
};
|
|
163
134
|
}
|
|
164
135
|
|
|
165
|
-
/**
|
|
166
|
-
* Extract package references from .csproj content
|
|
167
|
-
*/
|
|
168
136
|
extractPackages(csprojContent) {
|
|
169
137
|
const packages = [];
|
|
170
138
|
const packageRegex = /<PackageReference\s+Include="([^"]+)"\s+Version="([^"]+)"/g;
|
|
@@ -180,18 +148,13 @@ export class PackageValidator {
|
|
|
180
148
|
return packages;
|
|
181
149
|
}
|
|
182
150
|
|
|
183
|
-
/**
|
|
184
|
-
* Validate single package against compatibility matrix
|
|
185
|
-
*/
|
|
186
151
|
validatePackage(pkg, dotnetVersion) {
|
|
187
152
|
const rule = COMPATIBILITY_MATRIX[pkg.name];
|
|
188
153
|
|
|
189
154
|
if (!rule) {
|
|
190
|
-
// Package not in matrix - no validation
|
|
191
155
|
return null;
|
|
192
156
|
}
|
|
193
157
|
|
|
194
|
-
// Check if deprecated
|
|
195
158
|
if (rule.deprecated) {
|
|
196
159
|
return {
|
|
197
160
|
level: 'error',
|
|
@@ -204,7 +167,6 @@ export class PackageValidator {
|
|
|
204
167
|
};
|
|
205
168
|
}
|
|
206
169
|
|
|
207
|
-
// Check if version is sufficient
|
|
208
170
|
const requiredVersion = rule[dotnetVersion];
|
|
209
171
|
|
|
210
172
|
if (!requiredVersion) {
|
|
@@ -235,13 +197,10 @@ export class PackageValidator {
|
|
|
235
197
|
};
|
|
236
198
|
}
|
|
237
199
|
|
|
238
|
-
return null;
|
|
200
|
+
return null;
|
|
239
201
|
}
|
|
240
202
|
|
|
241
|
-
|
|
242
|
-
* Compare semantic versions
|
|
243
|
-
* Returns: -1 (v1 < v2), 0 (equal), 1 (v1 > v2)
|
|
244
|
-
*/
|
|
203
|
+
// -1 (v1 < v2), 0 (equal), 1 (v1 > v2)
|
|
245
204
|
compareVersions(v1, v2) {
|
|
246
205
|
const parts1 = v1.split('.').map(p => parseInt(p.replace(/[^0-9]/g, '')) || 0);
|
|
247
206
|
const parts2 = v2.split('.').map(p => parseInt(p.replace(/[^0-9]/g, '')) || 0);
|
|
@@ -259,9 +218,6 @@ export class PackageValidator {
|
|
|
259
218
|
return 0;
|
|
260
219
|
}
|
|
261
220
|
|
|
262
|
-
/**
|
|
263
|
-
* Format validation results for console output
|
|
264
|
-
*/
|
|
265
221
|
formatResults(results) {
|
|
266
222
|
if (results.status === 'ok') {
|
|
267
223
|
console.log(chalk.green('✅ All packages are compatible'));
|
|
@@ -314,9 +270,6 @@ export class PackageValidator {
|
|
|
314
270
|
}
|
|
315
271
|
}
|
|
316
272
|
|
|
317
|
-
/**
|
|
318
|
-
* Auto-fix compatible issues
|
|
319
|
-
*/
|
|
320
273
|
async autoFix(results) {
|
|
321
274
|
const { execSync } = await import('child_process');
|
|
322
275
|
let fixedCount = 0;
|
|
@@ -340,9 +293,6 @@ export class PackageValidator {
|
|
|
340
293
|
}
|
|
341
294
|
}
|
|
342
295
|
|
|
343
|
-
/**
|
|
344
|
-
* Quick validation function (for imports)
|
|
345
|
-
*/
|
|
346
296
|
export async function validatePackages(projectPath = '.', options = {}) {
|
|
347
297
|
const validator = new PackageValidator(projectPath);
|
|
348
298
|
const results = await validator.validateAll();
|
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reuse Validators
|
|
3
|
-
*
|
|
4
|
-
* Cobrança determinística de reuso de código: símbolo novo cujo nome já existe
|
|
5
|
-
* no grafo do codebase, na mesma linguagem. É a terceira camada do ciclo
|
|
6
|
-
* PROSA → ARTEFATO → VERIFICAÇÃO (ver reuse-validator.js).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
export * from './reuse-validator.js';
|
|
10
2
|
export * from './symbol-scanner.js';
|
|
@@ -1,32 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* A terceira camada do ciclo de reuso:
|
|
5
|
-
*
|
|
6
|
-
* PROSA (morph-plan, personas) ──► ARTEFATO (seção de reuso do plano)
|
|
7
|
-
* ──► COBRANÇA (este arquivo)
|
|
8
|
-
*
|
|
9
|
-
* Prosa sozinha não segura — a skill de planejamento já mandava olhar o que
|
|
10
|
-
* 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 na tabela de reuso do plano rebaixa o
|
|
23
|
-
* achado a aviso: a decisão foi tomada no gate do plano, com justificativa
|
|
24
|
-
* em `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';
|
|
@@ -42,26 +16,15 @@ import {
|
|
|
42
16
|
} from './symbol-scanner.js';
|
|
43
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)
|
|
@@ -166,13 +109,6 @@ export function findDuplicateSymbols(declarations, existing, opts = {}) {
|
|
|
166
109
|
});
|
|
167
110
|
}
|
|
168
111
|
|
|
169
|
-
/**
|
|
170
|
-
* Vereditos de reuso do plano: a tabela `## Reuso` que o plan-check extraiu.
|
|
171
|
-
*
|
|
172
|
-
* @param {string} projectPath
|
|
173
|
-
* @param {string} featureName
|
|
174
|
-
* @returns {Map<string, {verdict: string, target?: string}>}
|
|
175
|
-
*/
|
|
176
112
|
export function readReuseVerdicts(projectPath, featureName) {
|
|
177
113
|
const verdicts = new Map();
|
|
178
114
|
if (!featureName) return verdicts;
|
|
@@ -187,17 +123,8 @@ export function readReuseVerdicts(projectPath, featureName) {
|
|
|
187
123
|
return verdicts;
|
|
188
124
|
}
|
|
189
125
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*
|
|
193
|
-
* Duas fontes, unidas, porque cada uma é cega no momento em que a outra
|
|
194
|
-
* enxerga: os `arquivos:` das fatias são o contrato do plano e sobrevivem ao
|
|
195
|
-
* commit; `git status` pega o que o plano não previu. Sem git, sem drama.
|
|
196
|
-
*
|
|
197
|
-
* @param {string} projectPath
|
|
198
|
-
* @param {string} featureName
|
|
199
|
-
* @returns {string[]} paths relativos ao projeto, com barra normal
|
|
200
|
-
*/
|
|
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.
|
|
201
128
|
export function collectChangedFiles(projectPath, featureName) {
|
|
202
129
|
const files = new Set();
|
|
203
130
|
for (const slice of readPlanChecks(projectPath, featureName).checks?.slices ?? []) {
|
|
@@ -215,11 +142,7 @@ export function collectChangedFiles(projectPath, featureName) {
|
|
|
215
142
|
});
|
|
216
143
|
for (const line of out.split(/\r?\n/)) {
|
|
217
144
|
if (!line.trim()) continue;
|
|
218
|
-
//
|
|
219
|
-
// Os paths vêm relativos à RAIZ do repositório — que no morph-spec é a
|
|
220
|
-
// própria árvore da feature (cada worktree é a sua raiz). Se algum dia
|
|
221
|
-
// não coincidir com `projectPath`, o `existsSync` adiante descarta o
|
|
222
|
-
// path: perde-se cobertura, nunca se acusa um arquivo que não existe.
|
|
145
|
+
// Rename vem como "XY antigo -> novo": só o destino interessa.
|
|
223
146
|
const path = line.slice(3).split(' -> ').pop().replace(/^"|"$/g, '');
|
|
224
147
|
if (path) files.add(path.replace(/\\/g, '/'));
|
|
225
148
|
}
|
|
@@ -230,17 +153,6 @@ export function collectChangedFiles(projectPath, featureName) {
|
|
|
230
153
|
return [...files];
|
|
231
154
|
}
|
|
232
155
|
|
|
233
|
-
/**
|
|
234
|
-
* Validador plugado no `validation-runner` (e por ele no nó `validators` do
|
|
235
|
-
* `morph-spec verify`).
|
|
236
|
-
*
|
|
237
|
-
* @param {string} projectPath
|
|
238
|
-
* @param {string} featureName
|
|
239
|
-
* @param {object} [options]
|
|
240
|
-
* @param {string[]} [options.changedFiles] - override da coleta (testes)
|
|
241
|
-
* @param {object} [options._graph] - grafo já carregado (testes)
|
|
242
|
-
* @returns {Promise<{errors: number, warnings: number, issues: object[]}>}
|
|
243
|
-
*/
|
|
244
156
|
export async function validateReuse(projectPath, featureName = null, options = {}) {
|
|
245
157
|
const result = { errors: 0, warnings: 0, issues: [] };
|
|
246
158
|
|
|
@@ -250,11 +162,9 @@ export async function validateReuse(projectPath, featureName = null, options = {
|
|
|
250
162
|
})();
|
|
251
163
|
|
|
252
164
|
if (!graph) {
|
|
253
|
-
//
|
|
254
|
-
// segue em frente. Um validador que reprovasse aqui transformaria uma
|
|
255
|
-
// dependência opcional em pré-requisito.
|
|
165
|
+
// Sem grafo segue em frente: reprovar faria de uma dependência opcional um pré-requisito.
|
|
256
166
|
result._skipReason = 'sem grafo de codebase — `morph-spec graph build` habilita esta verificação';
|
|
257
|
-
// "
|
|
167
|
+
// "Não pude checar", diferente de "nada a checar": o verify relata em voz alta.
|
|
258
168
|
result._skipUnavailable = true;
|
|
259
169
|
return result;
|
|
260
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;
|