@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.
Files changed (304) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +4 -62
  4. package/bin/validate.js +1 -39
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +1 -1
  9. package/framework/MORPH.md +1 -1
  10. package/framework/agents.json +12 -4
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +6 -17
  14. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  15. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  16. package/framework/hooks/claude-code/core/chain.js +4 -65
  17. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  18. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  19. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  20. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  21. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  22. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  23. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  24. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  25. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  26. package/framework/hooks/claude-code/core/state-sync.js +11 -67
  27. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  29. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  30. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  31. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  32. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  33. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  34. package/framework/hooks/shared/activity-logger.js +9 -89
  35. package/framework/hooks/shared/agent-reap.js +2 -17
  36. package/framework/hooks/shared/build-check.js +6 -75
  37. package/framework/hooks/shared/claim-registry.js +9 -119
  38. package/framework/hooks/shared/command-tokens.js +6 -51
  39. package/framework/hooks/shared/comment-rules.js +5 -20
  40. package/framework/hooks/shared/comment-scanner.js +6 -25
  41. package/framework/hooks/shared/comment-stripper.js +11 -85
  42. package/framework/hooks/shared/e2e-check.js +0 -0
  43. package/framework/hooks/shared/errata.js +2 -15
  44. package/framework/hooks/shared/feature-locator.js +11 -95
  45. package/framework/hooks/shared/frontend-locations.js +1 -3
  46. package/framework/hooks/shared/gate-question.js +1 -7
  47. package/framework/hooks/shared/git-exclude.js +3 -20
  48. package/framework/hooks/shared/git-paths.js +8 -104
  49. package/framework/hooks/shared/graph-freshness.js +22 -166
  50. package/framework/hooks/shared/graph-refresh.js +21 -149
  51. package/framework/hooks/shared/heavy-command.js +4 -12
  52. package/framework/hooks/shared/hook-response.js +8 -83
  53. package/framework/hooks/shared/morph-cli.js +8 -52
  54. package/framework/hooks/shared/payload-utils.js +10 -132
  55. package/framework/hooks/shared/phase-utils.js +1 -42
  56. package/framework/hooks/shared/redact-secrets.js +4 -15
  57. package/framework/hooks/shared/reparse-scan.js +6 -66
  58. package/framework/hooks/shared/slice-progress.js +4 -23
  59. package/framework/hooks/shared/source-files.js +4 -17
  60. package/framework/hooks/shared/state-reader.js +13 -183
  61. package/framework/hooks/shared/state-writer.js +8 -52
  62. package/framework/hooks/shared/stdin-reader.js +1 -11
  63. package/framework/hooks/shared/telemetry-logger.js +7 -67
  64. package/framework/hooks/shared/transcript-usage.js +7 -29
  65. package/framework/hooks/shared/worktree-root.js +7 -29
  66. package/framework/rules/csharp-standards.md +1 -1
  67. package/framework/rules/frontend-standards.md +1 -0
  68. package/framework/rules/hangfire-standards.md +1 -1
  69. package/framework/rules/neon-standards.md +1 -1
  70. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  71. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  72. package/framework/skills/core/morph-apply/SKILL.md +2 -2
  73. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  74. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  75. package/framework/skills/core/morph-media/SKILL.md +1 -1
  76. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  77. package/framework/skills/core/morph-review/SKILL.md +1 -1
  78. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  79. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  80. package/framework/standards/STANDARDS.json +34 -12
  81. package/framework/standards/STANDARDS.meta.json +16 -2
  82. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  83. package/framework/standards/ai-agents/agent-session.md +1 -1
  84. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  85. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  86. package/framework/standards/ai-agents/guardrails.md +1 -1
  87. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  88. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  89. package/framework/standards/ai-agents/production.md +1 -1
  90. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  91. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  92. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  93. package/framework/standards/ai-agents/setup.md +2 -2
  94. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  95. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  96. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  97. package/framework/standards/ai-agents/workflows.md +1 -1
  98. package/framework/standards/architecture/code-comments.md +1 -1
  99. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  100. package/framework/standards/backend/api/validation.md +1 -1
  101. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  102. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  103. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  104. package/framework/standards/data/nosql/cache/redis.md +1 -1
  105. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  106. package/framework/standards/frontend/admin-app/forms.md +1 -1
  107. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  108. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  109. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  110. package/framework/templates/CLAUDE.md.core.template +3 -3
  111. package/framework/templates/REGISTRY.json +9 -9
  112. package/framework/templates/docs/ui-mockups.md +4 -0
  113. package/package.json +1 -1
  114. package/src/commands/context/context-refresh.js +0 -16
  115. package/src/commands/dashboard/dashboard.js +2 -8
  116. package/src/commands/e2e/e2e.js +17 -104
  117. package/src/commands/graph/affected.js +0 -14
  118. package/src/commands/graph/build.js +11 -50
  119. package/src/commands/graph/explain.js +0 -16
  120. package/src/commands/graph/status.js +1 -12
  121. package/src/commands/mcp/mcp-setup.js +4 -62
  122. package/src/commands/observability/cost.js +5 -57
  123. package/src/commands/observability/telemetry.js +1 -14
  124. package/src/commands/project/branch.js +4 -45
  125. package/src/commands/project/claim.js +4 -48
  126. package/src/commands/project/doctor.js +48 -302
  127. package/src/commands/project/env.js +1 -27
  128. package/src/commands/project/fleet.js +3 -32
  129. package/src/commands/project/init.js +4 -68
  130. package/src/commands/project/install-plugin-cmd.js +0 -1
  131. package/src/commands/project/standards.js +1 -62
  132. package/src/commands/project/status.js +0 -53
  133. package/src/commands/project/update.js +31 -239
  134. package/src/commands/project/worktree.js +108 -882
  135. package/src/commands/scaffold/scaffold.js +0 -25
  136. package/src/commands/state/advance.js +14 -85
  137. package/src/commands/state/approve.js +14 -96
  138. package/src/commands/state/archive.js +12 -64
  139. package/src/commands/state/auto-approval.js +2 -5
  140. package/src/commands/state/create.js +22 -109
  141. package/src/commands/state/delete.js +11 -63
  142. package/src/commands/state/finish.js +98 -475
  143. package/src/commands/state/gate-check.js +22 -132
  144. package/src/commands/state/gate-decision.js +3 -39
  145. package/src/commands/state/handoff.js +3 -30
  146. package/src/commands/state/list.js +14 -84
  147. package/src/commands/state/report-check.js +8 -58
  148. package/src/commands/state/retype.js +4 -26
  149. package/src/commands/validation/index.js +0 -3
  150. package/src/commands/validation/mutate.js +2 -5
  151. package/src/commands/validation/validate-feature.js +5 -94
  152. package/src/commands/validation/verify.js +15 -98
  153. package/src/core/paths/output-schema.js +2 -101
  154. package/src/core/state/feature-identity.js +1 -25
  155. package/src/core/state/gate-order.js +0 -27
  156. package/src/core/state/lifecycle-audit.js +8 -100
  157. package/src/core/state/state-manager.js +22 -304
  158. package/src/core/templates/template-registry.js +2 -97
  159. package/src/core/templates/template-renderer.js +13 -93
  160. package/src/core/templates/template-seed.js +0 -1
  161. package/src/core/work-types.js +1 -36
  162. package/src/dashboard/app/App.jsx +1 -8
  163. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  164. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  165. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  166. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  167. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  168. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  169. package/src/dashboard/app/components/Markdown.jsx +0 -9
  170. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  171. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  172. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  173. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  174. package/src/dashboard/app/hooks/usePref.js +1 -5
  175. package/src/dashboard/app/lib/format.js +0 -2
  176. package/src/dashboard/app/lib/phases.js +1 -5
  177. package/src/dashboard/app/lib/project.js +0 -12
  178. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  179. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  180. package/src/dashboard/app/pages/Features.jsx +1 -8
  181. package/src/dashboard/app/pages/Guide.jsx +0 -1
  182. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  183. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  184. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  185. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  186. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  187. package/src/dashboard/app/pages/Standards.jsx +2 -17
  188. package/src/dashboard/scanner/activity-scanner.js +10 -69
  189. package/src/dashboard/scanner/agents-scanner.js +1 -23
  190. package/src/dashboard/scanner/docs-scanner.js +0 -6
  191. package/src/dashboard/scanner/feature-status.js +2 -5
  192. package/src/dashboard/scanner/features-scanner.js +10 -66
  193. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  194. package/src/dashboard/scanner/index.js +0 -4
  195. package/src/dashboard/scanner/portfolio.js +5 -29
  196. package/src/dashboard/scanner/project-api.js +8 -54
  197. package/src/dashboard/scanner/rules-scanner.js +1 -2
  198. package/src/dashboard/scanner/skills-scanner.js +2 -12
  199. package/src/dashboard/scanner/standards-scanner.js +2 -6
  200. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  201. package/src/lib/ai/ai-checks.js +7 -52
  202. package/src/lib/ai/ai-project-scan.js +14 -108
  203. package/src/lib/ai/code-audit.js +7 -63
  204. package/src/lib/ai/pin-drift.js +8 -51
  205. package/src/lib/ai/registry-audit.js +11 -90
  206. package/src/lib/ci/ci-verify.js +4 -19
  207. package/src/lib/ci/github-ci.js +9 -37
  208. package/src/lib/ci/workflow-scaffold.js +2 -10
  209. package/src/lib/detectors/claude-config-detector.js +13 -74
  210. package/src/lib/detectors/index.js +4 -50
  211. package/src/lib/e2e/compose-env.js +5 -66
  212. package/src/lib/e2e/stack-teardown.js +6 -40
  213. package/src/lib/generators/architecture-scanner.js +18 -156
  214. package/src/lib/generators/handoff-generator.js +9 -53
  215. package/src/lib/generators/scaffold-generator.js +5 -101
  216. package/src/lib/generators/settings-generator.js +4 -37
  217. package/src/lib/git/branch-classifier.js +11 -84
  218. package/src/lib/git/default-branch.js +14 -169
  219. package/src/lib/git/evidence-staleness.js +1 -0
  220. package/src/lib/git/gh.js +2 -17
  221. package/src/lib/git/pending-prs.js +6 -37
  222. package/src/lib/git/pr-files.js +3 -28
  223. package/src/lib/git/pr-index.js +3 -34
  224. package/src/lib/git/tree-fingerprint.js +3 -15
  225. package/src/lib/git/tree-state.js +6 -33
  226. package/src/lib/graph/god-nodes.js +2 -22
  227. package/src/lib/graph/graph-health.js +3 -24
  228. package/src/lib/graph/graph-loader.js +5 -56
  229. package/src/lib/graph/graphify-runtime.js +7 -37
  230. package/src/lib/graph/graphifyignore.js +8 -74
  231. package/src/lib/graph/sanitize.js +16 -136
  232. package/src/lib/heavy/cleanup.js +1 -1
  233. package/src/lib/installers/effects.js +8 -93
  234. package/src/lib/installers/mcp-installer.js +9 -169
  235. package/src/lib/installers/memory-installer.js +4 -27
  236. package/src/lib/installers/templates-reconcile.js +5 -60
  237. package/src/lib/mutation/dotnet-introspection.js +20 -106
  238. package/src/lib/mutation/subset.js +1 -4
  239. package/src/lib/plan/artifact-vocabulary.js +5 -25
  240. package/src/lib/plan/checks.js +5 -31
  241. package/src/lib/plan/plan-parser.js +1 -6
  242. package/src/lib/projects/registry.js +7 -44
  243. package/src/lib/review-reports.js +5 -23
  244. package/src/lib/risk-detector.js +39 -173
  245. package/src/lib/specs/adr-index.js +6 -34
  246. package/src/lib/specs/frontmatter.js +2 -15
  247. package/src/lib/specs/gfm-table.js +1 -7
  248. package/src/lib/specs/revocations.js +1 -14
  249. package/src/lib/specs/screen-checklist.js +47 -124
  250. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  251. package/src/lib/stack/stack-profile.js +1 -28
  252. package/src/lib/stack/stack-scan.js +1 -8
  253. package/src/lib/stack-filter.js +4 -27
  254. package/src/lib/standards/registry.js +1 -7
  255. package/src/lib/standards/sections.js +2 -13
  256. package/src/lib/standards/verified-against.js +19 -191
  257. package/src/lib/tasks/eval-node.js +21 -169
  258. package/src/lib/tasks/linux-runner.js +18 -142
  259. package/src/lib/tasks/test-nodes.js +43 -240
  260. package/src/lib/tasks/test-runner.js +43 -341
  261. package/src/lib/telemetry/metrics.js +7 -36
  262. package/src/lib/telemetry/telemetry-reader.js +13 -125
  263. package/src/lib/telemetry/verify-event.js +2 -30
  264. package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
  265. package/src/lib/validators/code-budget/index.js +0 -7
  266. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  267. package/src/lib/validators/contracts/index.js +0 -6
  268. package/src/lib/validators/nextjs/index.js +0 -5
  269. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  270. package/src/lib/validators/packages/index.js +0 -6
  271. package/src/lib/validators/packages/package-validator.js +6 -56
  272. package/src/lib/validators/reuse/index.js +0 -8
  273. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  274. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  275. package/src/lib/validators/traceability-check.js +5 -63
  276. package/src/lib/validators/validation-runner.js +20 -125
  277. package/src/lib/validators/verify-runner.js +52 -358
  278. package/src/lib/work-type-detector.js +5 -67
  279. package/src/lib/worktree/docker-audit.js +17 -136
  280. package/src/lib/worktree/env-allocator.js +10 -98
  281. package/src/lib/worktree/feature-truth.js +12 -115
  282. package/src/lib/worktree/finish-backup.js +32 -242
  283. package/src/lib/worktree/fleet-scanner.js +13 -95
  284. package/src/lib/worktree/ignored-triage.js +15 -91
  285. package/src/lib/worktree/vscode-settings.js +2 -3
  286. package/src/lib/worktree/worktree-audit.js +21 -213
  287. package/src/lib/worktree/worktree-include.js +3 -5
  288. package/src/scripts/global-install.js +3 -9
  289. package/src/scripts/install-plugin.js +1 -13
  290. package/src/scripts/setup-infra.js +17 -147
  291. package/src/utils/agents-installer.js +28 -275
  292. package/src/utils/banner.js +4 -25
  293. package/src/utils/claude-md-injector.js +1 -37
  294. package/src/utils/claude-settings-manager.js +1 -52
  295. package/src/utils/file-copier.js +11 -95
  296. package/src/utils/hooks-installer.js +21 -298
  297. package/src/utils/skills-installer.js +4 -101
  298. package/src/utils/version-checker.js +4 -83
  299. package/framework/commands/morph-proposal.md +0 -9
  300. package/src/commands/utils/index.js +0 -3
  301. package/src/lib/detectors/design-system-detector.js +0 -188
  302. package/src/lib/generators/design-system-generator.js +0 -508
  303. package/src/lib/specs/old-format.js +0 -68
  304. 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
- * O hook git do próprio graphify (`graphify hook install`, que o setup-infra
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
- * O que o refresh automático faz por esta árvore. Ele cobre só a árvore
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
- * Descoberta do runtime Python e argv de instalação do graphify.
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
- * Pin exato: 0.9.50 é a única versão medida no eval e a base do fixture de
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
- * Geração stack-aware do .graphifyignore (ADR-004).
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
- * Escreve ou migra o .graphifyignore.
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
- * Saneamento determinístico do grafo, sem LLM (ADR-003 + errata D2/D3).
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
- * Código de teste — o predicado ÚNICO de "isto é teste" do grafo e do
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; // já consultável pelo caminho exato
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
- * `dispatches_to` que o graphify deixou de emitir por causa de um dublê de
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
- // D3: uma aresta nascida num .cs só pode apontar para o candidato .cs.
252
- // Recupera 54% dos casos ambíguos sem LLM e sem heurística de grau — que é
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 = [];
@@ -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 { /* stack sem passo de build */ }
17
+ } catch {}
18
18
  try {
19
19
  return readdirSync(projectPath).some((n) => /\.(sln|slnx|csproj)$/i.test(n));
20
20
  } catch {