@polymorphism-tech/morph-spec 9.0.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Auditoria do grafo para o `morph-spec doctor`.
|
|
3
|
-
*
|
|
4
|
-
* Nunca devolve estado de erro: o grafo é opcional (ADR-002) e não pode pintar
|
|
5
|
-
* de vermelho o doctor de um projeto que não o usa. O pior veredito é `warn`.
|
|
6
|
-
*
|
|
7
|
-
* @module graph-health
|
|
8
|
-
*/
|
|
1
|
+
// Nunca devolve erro: o grafo é opcional e não pode pintar de vermelho o doctor de quem não o usa.
|
|
9
2
|
import { execFileSync } from 'node:child_process';
|
|
10
3
|
import { readFileSync } from 'node:fs';
|
|
11
4
|
import { join, resolve } from 'node:path';
|
|
@@ -16,17 +9,8 @@ import { GRAPHIFY_VERSION, detectGraphifyVersion } from './graphify-runtime.js';
|
|
|
16
9
|
|
|
17
10
|
const GRAPHIFY_HOOK_MARKER = /# graphify-(?:checkout-)?hook-start/;
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* instalava até a 8.37) está no repositório? Ele é danoso aqui: num commit
|
|
22
|
-
* depois de um fast-forward carimba o HEAD num grafo SEM os arquivos que o
|
|
23
|
-
* merge trouxe — "fresh" falso — e disputa o graph.json com o refresh do
|
|
24
|
-
* morph-spec. Lê o diretório de hooks que o git usa (`core.hooksPath`
|
|
25
|
-
* incluído).
|
|
26
|
-
*
|
|
27
|
-
* @param {string} projectPath
|
|
28
|
-
* @returns {boolean}
|
|
29
|
-
*/
|
|
12
|
+
// O hook git do próprio graphify é danoso: depois de um fast-forward carimba o HEAD num grafo sem os
|
|
13
|
+
// arquivos do merge ("fresh" falso) e disputa o graph.json com o refresh. Respeita `core.hooksPath`.
|
|
30
14
|
export function graphifyGitHookInstalled(projectPath) {
|
|
31
15
|
let dir;
|
|
32
16
|
try {
|
|
@@ -46,11 +30,6 @@ export function graphifyGitHookInstalled(projectPath) {
|
|
|
46
30
|
});
|
|
47
31
|
}
|
|
48
32
|
|
|
49
|
-
/**
|
|
50
|
-
* @param {string} projectPath
|
|
51
|
-
* @param {{_version?: () => string|null}} [deps]
|
|
52
|
-
* @returns {{status: 'ok'|'warn'|'absent', msg: string, details: string[]}}
|
|
53
|
-
*/
|
|
54
33
|
export function auditGraph(projectPath, deps = {}) {
|
|
55
34
|
const installed = (deps._version ?? detectGraphifyVersion)();
|
|
56
35
|
const loaded = loadGraph(projectPath);
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Leitura fail-open do graph.json produzido pelo graphify.
|
|
3
|
-
*
|
|
4
|
-
* Medido em 2026-08-27 sobre o MORPH_GHLBrain (11,46 MB, 6.829 nós, 15.668
|
|
5
|
-
* arestas): JSON.parse leva 52 ms e 28 MB de heap. Streaming e cache são
|
|
6
|
-
* desnecessários nesse porte — se um dia deixarem de ser, a troca acontece só
|
|
7
|
-
* aqui, porque este módulo é a única porta de leitura do grafo.
|
|
8
|
-
*
|
|
9
|
-
* Nada aqui lança: ausência de grafo é um estado válido do projeto (ADR-002).
|
|
10
|
-
*
|
|
11
|
-
* @module graph-loader
|
|
12
|
-
*/
|
|
1
|
+
// A única porta de leitura do grafo, e nada aqui lança: ausência de grafo é um estado válido do projeto.
|
|
13
2
|
import { readFileSync, existsSync } from 'node:fs';
|
|
14
3
|
import { join } from 'node:path';
|
|
15
4
|
import {
|
|
@@ -21,18 +10,10 @@ import {
|
|
|
21
10
|
import { isSecondaryWorktree, workTreeTop } from '../../../framework/hooks/shared/git-paths.js';
|
|
22
11
|
import { GRAPHIFY_PACKAGE, GRAPHIFY_VERSION } from './graphify-runtime.js';
|
|
23
12
|
|
|
24
|
-
/**
|
|
25
|
-
* @param {string} projectPath
|
|
26
|
-
* @returns {string} caminho canônico do graph.json do projeto
|
|
27
|
-
*/
|
|
28
13
|
export function resolveGraphPath(projectPath) {
|
|
29
14
|
return join(projectPath, 'graphify-out', 'graph.json');
|
|
30
15
|
}
|
|
31
16
|
|
|
32
|
-
/**
|
|
33
|
-
* @param {string} projectPath
|
|
34
|
-
* @returns {{ok: boolean, reason?: 'missing'|'unreadable'|'malformed', graph?: object, path: string}}
|
|
35
|
-
*/
|
|
36
17
|
export function loadGraph(projectPath) {
|
|
37
18
|
const path = resolveGraphPath(projectPath);
|
|
38
19
|
if (!existsSync(path)) return { ok: false, reason: 'missing', path };
|
|
@@ -57,18 +38,7 @@ export function loadGraph(projectPath) {
|
|
|
57
38
|
}
|
|
58
39
|
}
|
|
59
40
|
|
|
60
|
-
|
|
61
|
-
* O frescor do grafo, pela regra única (framework/hooks/shared/graph-freshness.js):
|
|
62
|
-
* o carimbo e o registro da última rodada contra o HEAD, e as regras do
|
|
63
|
-
* `.graphifyignore` por conteúdo. Um grafo velho diz "não existe" sobre o que
|
|
64
|
-
* o último commit trouxe — quem consome precisa saber, ou conclui "ninguém usa
|
|
65
|
-
* isso" a partir dele.
|
|
66
|
-
*
|
|
67
|
-
* @param {string} projectPath
|
|
68
|
-
* @param {object} graph - o graph.json já parseado (o carimbo sai dele)
|
|
69
|
-
* @param {{_head?: (projectPath: string) => string|null}} [deps]
|
|
70
|
-
* @returns {{builtAtCommit: string|null, headCommit: string|null, state: 'fresh'|'stale'|'unknown', reason?: 'ignore-changed'|'rules-unknown'}}
|
|
71
|
-
*/
|
|
41
|
+
// Um grafo velho diz "não existe" sobre o que o último commit trouxe: quem consome precisa saber.
|
|
72
42
|
export function getFreshness(projectPath, graph, deps = {}) {
|
|
73
43
|
const f = graphFreshness(projectPath, {
|
|
74
44
|
graph,
|
|
@@ -84,12 +54,6 @@ export function getFreshness(projectPath, graph, deps = {}) {
|
|
|
84
54
|
|
|
85
55
|
const short = (sha) => (sha ? String(sha).slice(0, 8) : '?');
|
|
86
56
|
|
|
87
|
-
/**
|
|
88
|
-
* O porquê de um grafo stale, em uma linha — carimbo antigo ou regras.
|
|
89
|
-
*
|
|
90
|
-
* @param {{builtAtCommit: string|null, headCommit: string|null, reason?: string}} freshness
|
|
91
|
-
* @returns {string}
|
|
92
|
-
*/
|
|
93
57
|
export function staleCause(freshness) {
|
|
94
58
|
if (freshness.reason === 'ignore-changed') {
|
|
95
59
|
return 'as regras do .graphifyignore mudaram depois da extração';
|
|
@@ -100,15 +64,8 @@ export function staleCause(freshness) {
|
|
|
100
64
|
return `construído em ${short(freshness.builtAtCommit)}, HEAD é ${short(freshness.headCommit)}`;
|
|
101
65
|
}
|
|
102
66
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* principal (`auto`); numa worktree, `running` é sempre false — um lock vivo
|
|
106
|
-
* ali seria o refresh da raiz. `last` é a última rodada registrada para esta
|
|
107
|
-
* árvore: em background, manual ou a semeadura.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} projectPath
|
|
110
|
-
* @returns {{auto: boolean, running: boolean, since: string|null, pid: number|null, last: {ok: boolean, exit: number|null, head: string|null, ts: string, unavailable?: boolean, seeded?: boolean}|null}}
|
|
111
|
-
*/
|
|
67
|
+
// O refresh automático cobre só a árvore principal; numa worktree `running` é sempre false, porque um
|
|
68
|
+
// lock vivo ali seria o refresh da raiz.
|
|
112
69
|
export function refreshState(projectPath) {
|
|
113
70
|
const auto = isPrimaryTree(projectPath);
|
|
114
71
|
const lock = auto ? readRefreshLock(projectPath) : null;
|
|
@@ -129,15 +86,7 @@ export function refreshState(projectPath) {
|
|
|
129
86
|
};
|
|
130
87
|
}
|
|
131
88
|
|
|
132
|
-
/**
|
|
133
|
-
* A frase de grafo stale — a mesma no status, no explain, no affected e no
|
|
134
|
-
* doctor, e verdadeira sobre o mecanismo: o refresh em background só cobre a
|
|
135
|
-
* raiz, disparado pelos hooks do morph-spec; a worktree só anda à mão.
|
|
136
|
-
*
|
|
137
|
-
* @param {string} projectPath
|
|
138
|
-
* @param {{builtAtCommit?: string|null, headCommit?: string|null}} [freshness]
|
|
139
|
-
* @returns {string}
|
|
140
|
-
*/
|
|
89
|
+
/** A frase única de grafo stale do status, explain, affected e doctor. */
|
|
141
90
|
export function describeStale(projectPath, freshness = {}) {
|
|
142
91
|
const tree = workTreeTop(projectPath) ?? projectPath;
|
|
143
92
|
const state = refreshState(projectPath);
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* O binário instalado chama-se `graphify` (e `graphify-mcp` para o servidor
|
|
5
|
-
* MCP). NUNCA use `python -m graphify.serve`, que é o que o README oficial
|
|
6
|
-
* manda: com `uv tool install` o pacote vive num venv isolado e o Python do
|
|
7
|
-
* sistema não o enxerga (ModuleNotFoundError) — medido no eval, Eixo 2.
|
|
8
|
-
*
|
|
9
|
-
* Toda descoberta é injetável (`_which`, `_run`) para que o teste cubra Windows
|
|
10
|
-
* e POSIX sem depender do PATH da máquina que roda o CI.
|
|
11
|
-
*
|
|
12
|
-
* @module graphify-runtime
|
|
13
|
-
*/
|
|
1
|
+
// O binário é `graphify` (e `graphify-mcp`). NUNCA `python -m graphify.serve`, como manda o README oficial:
|
|
2
|
+
// com `uv tool install` o pacote vive num venv isolado que o Python do sistema não enxerga.
|
|
14
3
|
import { execFileSync } from 'node:child_process';
|
|
15
4
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* regressão. O graphify é pré-1.0 com release quase diária, então semver não
|
|
19
|
-
* garante nada — bump é decisão consciente, com re-run do fixture.
|
|
20
|
-
*/
|
|
5
|
+
// Pin exato: o graphify é pré-1.0 com release quase diária e semver não garante nada; subir é decisão
|
|
6
|
+
// consciente, com o fixture de regressão regerado.
|
|
21
7
|
export const GRAPHIFY_VERSION = '0.9.50';
|
|
22
8
|
export const GRAPHIFY_PACKAGE = 'graphifyy';
|
|
23
9
|
export const GRAPHIFY_BIN = 'graphify';
|
|
@@ -38,14 +24,7 @@ function whichSync(bin) {
|
|
|
38
24
|
}
|
|
39
25
|
}
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
* uv > pipx > python3 > python. `kind: null` significa "sem Python": o step do
|
|
43
|
-
* setup-infra registra e segue, e o framework continua funcionando por grep
|
|
44
|
-
* (ADR-002).
|
|
45
|
-
*
|
|
46
|
-
* @param {{_which?: (bin: string) => string|null}} [deps]
|
|
47
|
-
* @returns {{kind: 'uv'|'pipx'|'python3'|'python'|null, bin: string|null}}
|
|
48
|
-
*/
|
|
27
|
+
// `kind: null` é "sem Python": o setup registra e segue, e o framework continua funcionando por grep.
|
|
49
28
|
export function detectInstaller(deps = {}) {
|
|
50
29
|
const which = deps._which ?? whichSync;
|
|
51
30
|
for (const kind of PREFERENCE) {
|
|
@@ -60,13 +39,7 @@ export function detectInstaller(deps = {}) {
|
|
|
60
39
|
return { kind: null, bin: null };
|
|
61
40
|
}
|
|
62
41
|
|
|
63
|
-
|
|
64
|
-
* @param {'uv'|'pipx'|'python3'|'python'} kind
|
|
65
|
-
* @param {{withSql: boolean}} opts sem o extra `[sql]` os arquivos .sql são
|
|
66
|
-
* ignorados em silêncio (92 no ProspectPRO) — só um aviso no meio do output
|
|
67
|
-
* denuncia
|
|
68
|
-
* @returns {{cmd: string, args: string[]}}
|
|
69
|
-
*/
|
|
42
|
+
// Sem o extra `[sql]` os arquivos .sql são ignorados em silêncio.
|
|
70
43
|
export function installArgs(kind, { withSql }) {
|
|
71
44
|
const spec = `${GRAPHIFY_PACKAGE}${withSql ? '[sql]' : ''}==${GRAPHIFY_VERSION}`;
|
|
72
45
|
switch (kind) {
|
|
@@ -82,10 +55,7 @@ export function installArgs(kind, { withSql }) {
|
|
|
82
55
|
}
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
/**
|
|
86
|
-
* @param {{_run?: () => string}} [deps]
|
|
87
|
-
* @returns {string|null} versão instalada, ou null se o binário não responde
|
|
88
|
-
*/
|
|
58
|
+
/** Versão instalada, ou null se o binário não responde. */
|
|
89
59
|
export function detectGraphifyVersion(deps = {}) {
|
|
90
60
|
const run = deps._run ?? (() => execFileSync(GRAPHIFY_BIN, ['--version'], {
|
|
91
61
|
encoding: 'utf-8',
|
|
@@ -1,32 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Filtrar teste não é cosmético: sem isso o god node #1 do MORPH_GHLBrain é
|
|
5
|
-
* `FakeGhlToolbelt`, um dublê. E o padrão genérico `tests/` NÃO serve em .NET:
|
|
6
|
-
* no ProspectPRO os testes vivem em `src/backend/ProspectPRO.Tests/`. Com
|
|
7
|
-
* `**\/*.Tests/` o grafo caiu de 10.450 para 7.384 nós — 30% era ruído de teste.
|
|
8
|
-
*
|
|
9
|
-
* Mas dublê, fixture e helper de teste precisam ser ACHÁVEIS: são o que um
|
|
10
|
-
* teste novo deveria reusar, e fora do grafo `graph explain FakeX` dá
|
|
11
|
-
* not-found. Por isso o conteúdo dos projetos de teste sai e esses arquivos
|
|
12
|
-
* voltam. A forma é ditada pela regra do gitignore que o graphify 0.9.50 segue
|
|
13
|
-
* (detect.py `_is_ignored`): um `!` não reinclui nada sob um diretório já
|
|
14
|
-
* excluído. Medido num fixture: `**\/*.Tests/` + `!**\/Fakes/` não traz nada de
|
|
15
|
-
* volta; excluir o CONTEÚDO (`**\/*.Tests/**`), reincluir os diretórios
|
|
16
|
-
* (`!**\/*.Tests/**\/`) e então os helpers traz. `bin/`/`obj/` vêm DEPOIS das
|
|
17
|
-
* reinclusões — antes, a reinclusão de diretório os devolveria (`obj/` tem
|
|
18
|
-
* `.cs` gerado). No MORPH_GHLBrain: +9,6% de nós (11.591 → 12.706), 94
|
|
19
|
-
* arquivos de teste, nenhum de bin/obj.
|
|
20
|
-
*
|
|
21
|
-
* Os dublês quebram o guard de "implementador único" do `dispatches_to` do
|
|
22
|
-
* graphify (de 122 para 5 arestas no GHLBrain); o saneamento as recupera
|
|
23
|
-
* (sanitize.js `recoverDispatch`) e tira o teste dos god nodes.
|
|
24
|
-
*
|
|
25
|
-
* Em Next.js o problema é outro: metade dos nós vinha de JSON de configuração,
|
|
26
|
-
* e os god nodes viravam `dependencies` e `compilerOptions`.
|
|
27
|
-
*
|
|
28
|
-
* @module graphifyignore
|
|
29
|
-
*/
|
|
1
|
+
// Teste sai do grafo, mas dublês e helpers voltam: são o que um teste novo reusa. Pela regra do gitignore
|
|
2
|
+
// um `!` não reinclui nada sob diretório excluído, então exclui-se o conteúdo, reincluem-se os diretórios e
|
|
3
|
+
// então os helpers; `bin/`/`obj/` vêm depois, senão a reinclusão os devolveria.
|
|
30
4
|
import { writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
31
5
|
import { execFileSync } from 'node:child_process';
|
|
32
6
|
import { join } from 'node:path';
|
|
@@ -60,10 +34,7 @@ const WEB_IGNORED = [
|
|
|
60
34
|
|
|
61
35
|
const HEADER = '# Gerado por morph-spec (ADR-004). Editável: um arquivo editado nunca é sobrescrito.';
|
|
62
36
|
|
|
63
|
-
|
|
64
|
-
* O que a versão anterior gerava — os testes .NET inteiros fora. Só existe
|
|
65
|
-
* para reconhecer um arquivo que ninguém editou e trocá-lo pelo atual.
|
|
66
|
-
*/
|
|
37
|
+
// O que a geração anterior escrevia: só reconhece um arquivo que ninguém editou, para trocá-lo.
|
|
67
38
|
const PREVIOUS = {
|
|
68
39
|
header: '# Gerado por morph-spec (ADR-004). Editável: o CLI não sobrescreve.',
|
|
69
40
|
dotnetTitle: '# .NET — testes em qualquer nível (tests/ genérico não serve)',
|
|
@@ -99,11 +70,6 @@ function render(projectTags, { header, dotnetTitle, dotnet }) {
|
|
|
99
70
|
return `${lines.join('\n')}\n`;
|
|
100
71
|
}
|
|
101
72
|
|
|
102
|
-
/**
|
|
103
|
-
* @param {string[]} [projectTags] saída de `getProjectTags(config)` — stack
|
|
104
|
-
* splitado por `-`/`+` mais as integrations
|
|
105
|
-
* @returns {string} conteúdo completo do .graphifyignore
|
|
106
|
-
*/
|
|
107
73
|
export function buildGraphifyignore(projectTags = []) {
|
|
108
74
|
return render(projectTags, {
|
|
109
75
|
header: HEADER,
|
|
@@ -114,28 +80,8 @@ export function buildGraphifyignore(projectTags = []) {
|
|
|
114
80
|
|
|
115
81
|
const normalize = (text) => String(text).replace(/^/, '').replace(/\r\n/g, '\n');
|
|
116
82
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*
|
|
120
|
-
* ausente → escreve o atual
|
|
121
|
-
* idêntico ao que a versão anterior
|
|
122
|
-
* gerava para as mesmas tags → troca pelo atual (`migrated`) —
|
|
123
|
-
* só se as REGRAS mudam (em
|
|
124
|
-
* projeto sem .NET a diferença é
|
|
125
|
-
* só o cabeçalho, e reescrever
|
|
126
|
-
* seria um diff à toa num arquivo
|
|
127
|
-
* versionado)
|
|
128
|
-
* qualquer outra coisa → é do usuário: não toca, e em
|
|
129
|
-
* projeto .NET que ainda exclui os
|
|
130
|
-
* testes inteiros devolve em
|
|
131
|
-
* `outdated` a troca a fazer
|
|
132
|
-
*
|
|
133
|
-
* @param {string} projectPath
|
|
134
|
-
* @param {string[]} [projectTags]
|
|
135
|
-
* @param {{fx?: {writeFile: (path: string, content: string) => void}}} [opts]
|
|
136
|
-
* coletor de efeitos do `update` — com ele, o `--dry-run` prevê a escrita
|
|
137
|
-
* @returns {{written: boolean, migrated?: boolean, outdated?: {remove: string[], add: string[]}, path: string}}
|
|
138
|
-
*/
|
|
83
|
+
// Ausente → escreve. Idêntico ao que a geração anterior escrevia → migra, só se as regras mudam.
|
|
84
|
+
// Qualquer outra coisa é do usuário: não toca, e em .NET devolve em `outdated` a troca a fazer.
|
|
139
85
|
export function writeGraphifyignore(projectPath, projectTags = [], { fx } = {}) {
|
|
140
86
|
const path = join(projectPath, '.graphifyignore');
|
|
141
87
|
const current = buildGraphifyignore(projectTags);
|
|
@@ -172,26 +118,14 @@ export function writeGraphifyignore(projectPath, projectTags = [], { fx } = {})
|
|
|
172
118
|
return { written: false, path };
|
|
173
119
|
}
|
|
174
120
|
|
|
175
|
-
/**
|
|
176
|
-
* A frase que todo chamador imprime para um `outdated` — uma só.
|
|
177
|
-
*
|
|
178
|
-
* @param {{remove: string[], add: string[]}} outdated
|
|
179
|
-
* @returns {string}
|
|
180
|
-
*/
|
|
121
|
+
/** A frase única que todo chamador imprime para um `outdated`. */
|
|
181
122
|
export function describeOutdatedIgnore(outdated) {
|
|
182
123
|
return `.graphifyignore editado mantém os testes .NET inteiros fora do grafo ` +
|
|
183
124
|
`(dublês e fixtures dão not-found). Troque ${outdated.remove.join(' ')} por, nesta ordem e ` +
|
|
184
125
|
`antes de bin/ e obj/: ${outdated.add.join(' ')} — depois, morph-spec graph refresh`;
|
|
185
126
|
}
|
|
186
127
|
|
|
187
|
-
|
|
188
|
-
* O `.graphifyignore` é versionado? Então uma migração só chega às worktrees
|
|
189
|
-
* e ao resto do time depois de commitada — cada checkout lê o seu. Fail-open:
|
|
190
|
-
* sem git, false.
|
|
191
|
-
*
|
|
192
|
-
* @param {string} projectPath
|
|
193
|
-
* @returns {boolean}
|
|
194
|
-
*/
|
|
128
|
+
// Versionado, uma migração só chega às worktrees depois de commitada. Fail-open: sem git, false.
|
|
195
129
|
export function graphifyignoreTracked(projectPath) {
|
|
196
130
|
try {
|
|
197
131
|
execFileSync('git', ['ls-files', '--error-unmatch', '--', '.graphifyignore'], {
|
|
@@ -1,26 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* O graphify 0.9.50 não implementa escopo hierárquico de namespace em C#
|
|
5
|
-
* (graphify/extractors/csharp.py:322-335): um filho que usa um tipo do
|
|
6
|
-
* namespace pai sem `using` explícito vira um STUB por arquivo, e o blast
|
|
7
|
-
* radius do nó real devolve zero consumidores onde existem dois. É falso
|
|
8
|
-
* negativo silencioso — o pior modo de falha possível antes de um hotfix.
|
|
9
|
-
*
|
|
10
|
-
* Três categorias de stub, medidas no MORPH_GHLBrain em 2026-08-27:
|
|
11
|
-
*
|
|
12
|
-
* label com 1 nó real (60 labels, 226 arestas) => coalesce no real
|
|
13
|
-
* label com 0 nós reais (126 labels, 2.479 arestas) => externo (BCL/NuGet)
|
|
14
|
-
* label com 2+ nós reais (22 labels, 93 arestas) => AMBÍGUO
|
|
15
|
-
*
|
|
16
|
-
* O terceiro grupo são símbolos REAIS do projeto (CatalogSchema, CatalogItem,
|
|
17
|
-
* Result, Error, KillSwitch — declarados em CatalogContracts.cs e vizinhos),
|
|
18
|
-
* não BCL. Filtrar "o que sobrou depois de coalescer" os descartaria em
|
|
19
|
-
* silêncio, reintroduzindo o falso negativo que este módulo existe para matar.
|
|
20
|
-
* Por isso o critério de externo é `real.length === 0`, nunca "remanescente".
|
|
21
|
-
*
|
|
22
|
-
* @module graph-sanitize
|
|
23
|
-
*/
|
|
1
|
+
// O graphify não resolve escopo hierárquico de namespace em C#: tipo do namespace pai vira stub por arquivo.
|
|
2
|
+
// Label com 1 nó real coalesce; 0 reais é externo; 2+ reais é ambíguo e são símbolos do projeto, por isso
|
|
3
|
+
// externo é `real.length === 0`, nunca "o que sobrou".
|
|
24
4
|
|
|
25
5
|
/** Arestas que contam como dependência para blast radius. */
|
|
26
6
|
export const USE_RELATIONS = new Set([
|
|
@@ -28,10 +8,7 @@ export const USE_RELATIONS = new Set([
|
|
|
28
8
|
'inherits', 'implements', 'extends', 'dispatches_to', 'indirect_call',
|
|
29
9
|
]);
|
|
30
10
|
|
|
31
|
-
/**
|
|
32
|
-
* Herança não é uso. No ProspectPRO, `IHandler` aparece em 193 arquivos no
|
|
33
|
-
* grep e tem 2 usos reais: é implementada por 98.
|
|
34
|
-
*/
|
|
11
|
+
/** Herança não é uso: uma interface muito implementada tem poucos usos reais. */
|
|
35
12
|
export const INHERITANCE_RELATIONS = new Set(['inherits', 'implements', 'extends']);
|
|
36
13
|
|
|
37
14
|
const LANG_BY_EXT = {
|
|
@@ -40,31 +17,15 @@ const LANG_BY_EXT = {
|
|
|
40
17
|
sql: 'sql', py: 'python', go: 'go', rs: 'rust', java: 'jvm', kt: 'jvm',
|
|
41
18
|
};
|
|
42
19
|
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
* validador de reuso. Pasta de projeto de teste em qualquer nível, em três
|
|
46
|
-
* formas, e só três, porque `[^/]*[Tt]ests?/` genérico também casaria
|
|
47
|
-
* `latest/` e `contests/`:
|
|
48
|
-
* - o segmento inteiro é `test`/`tests` (qualquer caixa)
|
|
49
|
-
* - termina em `.Test`/`.Tests` — a convenção .NET (ProspectPRO.Tests/)
|
|
50
|
-
* - termina em `Test`/`Tests` com T maiúsculo — PascalCase (AlvoTests/)
|
|
51
|
-
* mais o teste colocado ao lado do código em JS/TS: `__tests__/`,
|
|
52
|
-
* `*.test.*` e `*.spec.*`.
|
|
53
|
-
*/
|
|
20
|
+
// O predicado único de teste do grafo e do validador de reuso. Só três formas de pasta, porque
|
|
21
|
+
// `[^/]*[Tt]ests?/` genérico também casaria `latest/` e `contests/`.
|
|
54
22
|
const TEST_PATH = /(^|\/)__tests__\/|\.(test|spec)\.[jt]sx?$|(^|\/)(?:[Tt]ests?|[^/]*\.[Tt]ests?|[^/]*[a-z0-9_]Tests?)\//;
|
|
55
23
|
|
|
56
|
-
/**
|
|
57
|
-
* @param {string} sourceFile
|
|
58
|
-
* @returns {boolean} o arquivo é código de teste (dublê, fixture ou o teste em si)
|
|
59
|
-
*/
|
|
60
24
|
export function isTestPath(sourceFile) {
|
|
61
25
|
return TEST_PATH.test(String(sourceFile || '').replace(/\\/g, '/'));
|
|
62
26
|
}
|
|
63
27
|
|
|
64
|
-
/**
|
|
65
|
-
* @param {string} sourceFile
|
|
66
|
-
* @returns {string} família de linguagem do arquivo, ou '' quando desconhecida
|
|
67
|
-
*/
|
|
28
|
+
/** Família de linguagem do arquivo, ou '' quando desconhecida. */
|
|
68
29
|
export function languageOf(sourceFile) {
|
|
69
30
|
const m = /\.([a-z0-9]+)$/i.exec(sourceFile || '');
|
|
70
31
|
if (!m) return '';
|
|
@@ -73,29 +34,9 @@ export function languageOf(sourceFile) {
|
|
|
73
34
|
|
|
74
35
|
const normOf = (node) => node.norm_label ?? String(node.label ?? '').toLowerCase();
|
|
75
36
|
|
|
76
|
-
|
|
77
|
-
* Label sem os parênteses de chamada. O graphify rotula função como `tchau()`
|
|
78
|
-
* em JS/TS, mas quem consulta digita `tchau` — e em C# o mesmo símbolo aparece
|
|
79
|
-
* sem parênteses. Sem esta normalização, `graph affected tchau` devolve
|
|
80
|
-
* not-found num grafo que tem o símbolo.
|
|
81
|
-
*/
|
|
37
|
+
// O graphify rotula função como `tchau()` em JS/TS, mas quem consulta digita `tchau`, e em C# não há parênteses.
|
|
82
38
|
const baseOf = (norm) => norm.replace(/\(\s*\)$/, '');
|
|
83
39
|
|
|
84
|
-
/**
|
|
85
|
-
* @typedef {object} GraphIndex
|
|
86
|
-
* @property {Map<string, object>} byId
|
|
87
|
-
* @property {Map<string, {real: object[], stub: object[]}>} byLabel
|
|
88
|
-
* @property {Map<string, object[]>} byBaseLabel label sem `()` -> nós reais
|
|
89
|
-
* @property {Map<string, string>} coalesce stubId -> id do nó real
|
|
90
|
-
* @property {object[]} recovered `dispatches_to` recuperados (ver recoverDispatch)
|
|
91
|
-
*/
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Índice por id e por label + mapa de coalescência. Uma passada pelos nós.
|
|
95
|
-
*
|
|
96
|
-
* @param {{nodes: object[], links: object[]}} graph
|
|
97
|
-
* @returns {GraphIndex}
|
|
98
|
-
*/
|
|
99
40
|
export function buildIndex(graph) {
|
|
100
41
|
const byId = new Map();
|
|
101
42
|
const byLabel = new Map();
|
|
@@ -121,7 +62,7 @@ export function buildIndex(graph) {
|
|
|
121
62
|
}
|
|
122
63
|
if (entry.real.length === 0) continue;
|
|
123
64
|
const base = baseOf(key);
|
|
124
|
-
if (base === key) continue;
|
|
65
|
+
if (base === key) continue;
|
|
125
66
|
const bucket = byBaseLabel.get(base);
|
|
126
67
|
if (bucket) bucket.push(...entry.real);
|
|
127
68
|
else byBaseLabel.set(base, [...entry.real]);
|
|
@@ -137,21 +78,8 @@ const isCsharp = (node) => Boolean(node?.source_file) && /\.cs$/i.test(node.sour
|
|
|
137
78
|
/** Nome do método sem o ponto inicial e sem a assinatura, como o graphify casa. */
|
|
138
79
|
const methodName = (node) => String(node.label ?? '').trim().replace(/^\./, '').split('(')[0];
|
|
139
80
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
* teste. O resolvedor dele (graphify/csharp_dispatch.py) liga o método da
|
|
143
|
-
* interface ao da implementação só quando há EXATAMENTE um implementador — e
|
|
144
|
-
* um `FakeX : IX` no grafo é o segundo. Este é o mesmo algoritmo, com o guard
|
|
145
|
-
* contando só implementadores de produção. Medido no MORPH_GHLBrain: com os
|
|
146
|
-
* dublês no grafo o graphify cai de 122 para 5 arestas, e esta função devolve
|
|
147
|
-
* as 122 do grafo sem testes, nenhuma a mais.
|
|
148
|
-
*
|
|
149
|
-
* Aditivo: um par que o graphify já emitiu não é repetido.
|
|
150
|
-
*
|
|
151
|
-
* @param {GraphIndex} index
|
|
152
|
-
* @param {{links?: object[]}} graph
|
|
153
|
-
* @returns {object[]} arestas sintéticas, marcadas `_recovered: true`
|
|
154
|
-
*/
|
|
81
|
+
// O resolvedor do graphify liga interface a implementação só com EXATAMENTE um implementador, e um
|
|
82
|
+
// dublê `FakeX : IX` é o segundo. Mesmo algoritmo contando só produção; aditivo, nunca repete um par.
|
|
155
83
|
function recoverDispatch(index, graph) {
|
|
156
84
|
const implementers = new Map();
|
|
157
85
|
const methodsOf = new Map();
|
|
@@ -204,24 +132,12 @@ function recoverDispatch(index, graph) {
|
|
|
204
132
|
return recovered;
|
|
205
133
|
}
|
|
206
134
|
|
|
207
|
-
/**
|
|
208
|
-
* As arestas do grafo mais as recuperadas pelo saneamento — o conjunto que
|
|
209
|
-
* todo consumidor deve percorrer.
|
|
210
|
-
*
|
|
211
|
-
* @param {GraphIndex} index
|
|
212
|
-
* @param {{links?: object[]}} graph
|
|
213
|
-
* @returns {object[]}
|
|
214
|
-
*/
|
|
135
|
+
/** As arestas do grafo mais as recuperadas pelo saneamento: o conjunto que todo consumidor percorre. */
|
|
215
136
|
export function linksOf(index, graph) {
|
|
216
137
|
const own = graph.links ?? [];
|
|
217
138
|
return index.recovered?.length ? own.concat(index.recovered) : own;
|
|
218
139
|
}
|
|
219
140
|
|
|
220
|
-
/**
|
|
221
|
-
* @param {GraphIndex} index
|
|
222
|
-
* @param {string} normLabel label já normalizado (minúsculo)
|
|
223
|
-
* @returns {'resolved'|'coalescible'|'external'|'ambiguous'|'unknown'}
|
|
224
|
-
*/
|
|
225
141
|
export function classifyLabel(index, normLabel) {
|
|
226
142
|
const entry = index.byLabel.get(normLabel);
|
|
227
143
|
if (!entry) return 'unknown';
|
|
@@ -230,13 +146,6 @@ export function classifyLabel(index, normLabel) {
|
|
|
230
146
|
return entry.stub.length > 0 ? 'coalescible' : 'resolved';
|
|
231
147
|
}
|
|
232
148
|
|
|
233
|
-
/**
|
|
234
|
-
* Para onde uma aresta REALMENTE aponta depois do saneamento.
|
|
235
|
-
*
|
|
236
|
-
* @param {GraphIndex} index
|
|
237
|
-
* @param {object} link
|
|
238
|
-
* @returns {{id: string|null, kind: 'direct'|'coalesced'|'language-disambiguated'|'external'|'ambiguous'|'unknown'}}
|
|
239
|
-
*/
|
|
240
149
|
export function resolveEdgeTarget(index, link) {
|
|
241
150
|
const target = index.byId.get(link.target);
|
|
242
151
|
if (!target) return { id: null, kind: 'unknown' };
|
|
@@ -248,10 +157,8 @@ export function resolveEdgeTarget(index, link) {
|
|
|
248
157
|
const entry = index.byLabel.get(normOf(target));
|
|
249
158
|
if (!entry || entry.real.length === 0) return { id: null, kind: 'external' };
|
|
250
159
|
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
// a alternativa (b) rejeitada na ADR-003 por errar justamente nas interfaces
|
|
254
|
-
// muito referenciadas.
|
|
160
|
+
// Uma aresta nascida num .cs só pode apontar para o candidato .cs: desambigua sem heurística de grau,
|
|
161
|
+
// que erra justamente nas interfaces muito referenciadas.
|
|
255
162
|
const originLang = languageOf(link.source_file);
|
|
256
163
|
if (originLang) {
|
|
257
164
|
const matches = entry.real.filter((n) => languageOf(n.source_file) === originLang);
|
|
@@ -261,13 +168,7 @@ export function resolveEdgeTarget(index, link) {
|
|
|
261
168
|
return { id: null, kind: 'ambiguous' };
|
|
262
169
|
}
|
|
263
170
|
|
|
264
|
-
/**
|
|
265
|
-
* label -> id. Empate real devolve as opções em vez de escolher (ADR-003).
|
|
266
|
-
*
|
|
267
|
-
* @param {GraphIndex} index
|
|
268
|
-
* @param {string} label
|
|
269
|
-
* @returns {{ok: true, id: string, node: object}|{ok: false, reason: 'not-found'|'ambiguous', options: Array<{id: string, source_file: string}>}}
|
|
270
|
-
*/
|
|
171
|
+
/** Empate real devolve as opções em vez de escolher. */
|
|
271
172
|
export function resolveLabel(index, label) {
|
|
272
173
|
const key = String(label).toLowerCase();
|
|
273
174
|
const exact = index.byLabel.get(key);
|
|
@@ -290,19 +191,6 @@ export function resolveLabel(index, label) {
|
|
|
290
191
|
};
|
|
291
192
|
}
|
|
292
193
|
|
|
293
|
-
/**
|
|
294
|
-
* Blast radius saneado: quem depende de `id`.
|
|
295
|
-
*
|
|
296
|
-
* O valor medido não é recall — contra o grep, o grafo achou ZERO arquivos a
|
|
297
|
-
* mais. É precisão: 58% menos ruído em 12 símbolos do ProspectPRO (1.092
|
|
298
|
-
* arquivos no grep, 266 usos reais) e a classificação da relação.
|
|
299
|
-
*
|
|
300
|
-
* @param {GraphIndex} index
|
|
301
|
-
* @param {{links: object[]}} graph
|
|
302
|
-
* @param {string} id
|
|
303
|
-
* @param {{prod?: boolean, excludeInheritance?: boolean}} [opts]
|
|
304
|
-
* @returns {Array<{id: string, label: string, source_file: string, source_location: string, relation: string, test: boolean}>}
|
|
305
|
-
*/
|
|
306
194
|
export function affected(index, graph, id, opts = {}) {
|
|
307
195
|
const seen = new Set();
|
|
308
196
|
const out = [];
|
|
@@ -334,15 +222,7 @@ export function affected(index, graph, id, opts = {}) {
|
|
|
334
222
|
return out;
|
|
335
223
|
}
|
|
336
224
|
|
|
337
|
-
/**
|
|
338
|
-
* Vizinhança de um nó, para `graph explain`. Cada aresta carrega a procedência
|
|
339
|
-
* (`via`), então dá para ver o que o saneamento recuperou, e `test` diz se a
|
|
340
|
-
* outra ponta é código de teste.
|
|
341
|
-
*
|
|
342
|
-
* @param {GraphIndex} index
|
|
343
|
-
* @param {{links: object[]}} graph
|
|
344
|
-
* @param {string} id
|
|
345
|
-
*/
|
|
225
|
+
/** Cada aresta carrega a procedência (`via`); `test` diz se a outra ponta é código de teste. */
|
|
346
226
|
export function neighborhood(index, graph, id) {
|
|
347
227
|
const incoming = [];
|
|
348
228
|
const outgoing = [];
|
package/src/lib/heavy/cleanup.js
CHANGED
|
@@ -14,7 +14,7 @@ function hasDotnet(projectPath) {
|
|
|
14
14
|
const stack = config?.project?.stack;
|
|
15
15
|
try {
|
|
16
16
|
if (stack && getBuildSteps(stack, { cwd: projectPath }).some((s) => /\bdotnet\b/.test(s.command))) return true;
|
|
17
|
-
} catch {
|
|
17
|
+
} catch {}
|
|
18
18
|
try {
|
|
19
19
|
return readdirSync(projectPath).some((n) => /\.(sln|slnx|csproj)$/i.test(n));
|
|
20
20
|
} catch {
|