@polymorphism-tech/morph-spec 8.47.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/CLAUDE.md +51 -43
  2. package/README.md +39 -38
  3. package/bin/morph-spec.js +41 -113
  4. package/bin/validate.js +1 -147
  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 +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +86 -21
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-status.md +4 -4
  17. package/framework/docs/CLI.md +574 -286
  18. package/framework/evals/review-checklist.md +82 -41
  19. package/framework/hooks/README.md +10 -11
  20. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  21. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  22. package/framework/hooks/claude-code/core/chain.js +4 -65
  23. package/framework/hooks/claude-code/core/comment-guard.js +3 -17
  24. package/framework/hooks/claude-code/core/denylist.json +33 -3
  25. package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
  26. package/framework/hooks/claude-code/core/gate-guard.js +9 -78
  27. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  28. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  29. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  30. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  31. package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
  32. package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
  33. package/framework/hooks/claude-code/core/state-sync.js +15 -85
  34. package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
  35. package/framework/hooks/claude-code/core/validate-completion.js +37 -147
  36. package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
  37. package/framework/hooks/claude-code/core/worktree-context.js +15 -114
  38. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  39. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  40. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  41. package/framework/hooks/claude-code/statusline.py +55 -41
  42. package/framework/hooks/package.json +3 -0
  43. package/framework/hooks/shared/activity-logger.js +9 -89
  44. package/framework/hooks/shared/agent-reap.js +2 -17
  45. package/framework/hooks/shared/build-check.js +11 -76
  46. package/framework/hooks/shared/claim-registry.js +9 -119
  47. package/framework/hooks/shared/command-tokens.js +168 -44
  48. package/framework/hooks/shared/comment-rules.js +5 -20
  49. package/framework/hooks/shared/comment-scanner.js +11 -43
  50. package/framework/hooks/shared/comment-stripper.js +11 -85
  51. package/framework/hooks/shared/e2e-check.js +0 -0
  52. package/framework/hooks/shared/errata.js +59 -0
  53. package/framework/hooks/shared/feature-locator.js +11 -95
  54. package/framework/hooks/shared/frontend-locations.js +4 -0
  55. package/framework/hooks/shared/gate-question.js +12 -0
  56. package/framework/hooks/shared/git-exclude.js +3 -20
  57. package/framework/hooks/shared/git-paths.js +8 -104
  58. package/framework/hooks/shared/graph-freshness.js +22 -166
  59. package/framework/hooks/shared/graph-refresh.js +21 -149
  60. package/framework/hooks/shared/heavy-command.js +4 -12
  61. package/framework/hooks/shared/hook-response.js +8 -83
  62. package/framework/hooks/shared/morph-cli.js +8 -52
  63. package/framework/hooks/shared/payload-utils.js +10 -132
  64. package/framework/hooks/shared/phase-utils.js +14 -72
  65. package/framework/hooks/shared/redact-secrets.js +4 -15
  66. package/framework/hooks/shared/reparse-scan.js +6 -66
  67. package/framework/hooks/shared/slice-progress.js +99 -0
  68. package/framework/hooks/shared/source-files.js +4 -17
  69. package/framework/hooks/shared/spawn-command.js +23 -0
  70. package/framework/hooks/shared/state-reader.js +51 -247
  71. package/framework/hooks/shared/state-writer.js +9 -54
  72. package/framework/hooks/shared/stdin-reader.js +1 -11
  73. package/framework/hooks/shared/telemetry-logger.js +7 -67
  74. package/framework/hooks/shared/transcript-usage.js +7 -29
  75. package/framework/hooks/shared/worktree-root.js +7 -29
  76. package/framework/mcp/registry.json +3 -4
  77. package/framework/phases.json +23 -39
  78. package/framework/rules/csharp-standards.md +1 -1
  79. package/framework/rules/frontend-standards.md +1 -0
  80. package/framework/rules/hangfire-standards.md +1 -1
  81. package/framework/rules/neon-standards.md +1 -1
  82. package/framework/schemas/config.schema.json +14 -9
  83. package/framework/schemas/feature.schema.json +15 -50
  84. package/framework/skills/README.md +3 -4
  85. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  86. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  87. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  88. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  89. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  90. package/framework/skills/core/morph-media/SKILL.md +1 -1
  91. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  92. package/framework/skills/core/morph-review/SKILL.md +111 -136
  93. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  94. package/framework/skills/core/morph-uiux/SKILL.md +94 -152
  95. package/framework/standards/STANDARDS.json +109 -1219
  96. package/framework/standards/STANDARDS.meta.json +1319 -0
  97. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  98. package/framework/standards/ai-agents/agent-session.md +1 -1
  99. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  100. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  101. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  102. package/framework/standards/ai-agents/guardrails.md +1 -1
  103. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  104. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  105. package/framework/standards/ai-agents/production.md +1 -1
  106. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  107. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  108. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  109. package/framework/standards/ai-agents/setup.md +3 -3
  110. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  111. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  112. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  113. package/framework/standards/ai-agents/workflows.md +1 -1
  114. package/framework/standards/architecture/code-comments.md +7 -7
  115. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  116. package/framework/standards/backend/api/validation.md +1 -1
  117. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  118. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  119. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  120. package/framework/standards/data/nosql/cache/redis.md +1 -1
  121. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  122. package/framework/standards/frontend/admin-app/forms.md +1 -1
  123. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  124. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  125. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  126. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  127. package/framework/templates/CLAUDE.md.core.template +3 -3
  128. package/framework/templates/README.md +2 -3
  129. package/framework/templates/REGISTRY.json +15 -179
  130. package/framework/templates/docs/plan.md +72 -0
  131. package/framework/templates/docs/ui-mockups.md +4 -0
  132. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  134. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  135. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  136. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  137. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  138. package/package.json +1 -1
  139. package/src/commands/context/context-refresh.js +0 -16
  140. package/src/commands/dashboard/dashboard.js +2 -8
  141. package/src/commands/e2e/e2e.js +18 -105
  142. package/src/commands/graph/affected.js +0 -14
  143. package/src/commands/graph/build.js +11 -50
  144. package/src/commands/graph/explain.js +0 -16
  145. package/src/commands/graph/status.js +1 -12
  146. package/src/commands/mcp/mcp-setup.js +4 -62
  147. package/src/commands/observability/cost.js +17 -111
  148. package/src/commands/observability/telemetry.js +1 -14
  149. package/src/commands/project/branch.js +4 -45
  150. package/src/commands/project/claim.js +4 -48
  151. package/src/commands/project/doctor-procs.js +1 -1
  152. package/src/commands/project/doctor.js +53 -307
  153. package/src/commands/project/env.js +1 -27
  154. package/src/commands/project/fleet.js +6 -35
  155. package/src/commands/project/heavy.js +3 -3
  156. package/src/commands/project/init.js +8 -68
  157. package/src/commands/project/install-plugin-cmd.js +0 -1
  158. package/src/commands/project/setup-infra-cmd.js +2 -1
  159. package/src/commands/project/stack-scan.js +58 -0
  160. package/src/commands/project/standards.js +65 -72
  161. package/src/commands/project/status.js +28 -121
  162. package/src/commands/project/update.js +31 -223
  163. package/src/commands/project/worktree.js +109 -883
  164. package/src/commands/scaffold/scaffold.js +0 -25
  165. package/src/commands/state/advance.js +65 -120
  166. package/src/commands/state/approve.js +87 -229
  167. package/src/commands/state/archive.js +23 -139
  168. package/src/commands/state/auto-approval.js +65 -0
  169. package/src/commands/state/create.js +49 -109
  170. package/src/commands/state/delete.js +11 -63
  171. package/src/commands/state/finish.js +105 -494
  172. package/src/commands/state/gate-check.js +74 -183
  173. package/src/commands/state/gate-decision.js +8 -42
  174. package/src/commands/state/handoff.js +4 -31
  175. package/src/commands/state/list.js +21 -91
  176. package/src/commands/state/report-check.js +134 -338
  177. package/src/commands/state/retype.js +12 -28
  178. package/src/commands/validation/index.js +0 -3
  179. package/src/commands/validation/mutate.js +79 -19
  180. package/src/commands/validation/plan-check.js +81 -0
  181. package/src/commands/validation/validate-feature.js +52 -216
  182. package/src/commands/validation/verify.js +45 -160
  183. package/src/core/paths/output-schema.js +9 -155
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +2 -30
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +32 -395
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +21 -0
  191. package/src/core/work-types.js +10 -45
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
  194. package/src/dashboard/app/components/CommandPalette.jsx +4 -11
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +3 -9
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/pages/Activity.jsx +1 -1
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
  211. package/src/dashboard/app/pages/Features.jsx +9 -16
  212. package/src/dashboard/app/pages/Guide.jsx +2 -3
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +14 -83
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +1 -7
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +92 -113
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +4 -7
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/lib/ai/ai-checks.js +7 -52
  233. package/src/lib/ai/ai-project-scan.js +14 -108
  234. package/src/lib/ai/code-audit.js +7 -63
  235. package/src/lib/ai/pin-drift.js +8 -51
  236. package/src/lib/ai/registry-audit.js +11 -90
  237. package/src/lib/ci/ci-verify.js +14 -13
  238. package/src/lib/ci/github-ci.js +9 -37
  239. package/src/lib/ci/workflow-scaffold.js +2 -10
  240. package/src/lib/detectors/claude-config-detector.js +13 -74
  241. package/src/lib/detectors/index.js +8 -59
  242. package/src/lib/e2e/compose-env.js +5 -66
  243. package/src/lib/e2e/stack-teardown.js +6 -40
  244. package/src/lib/generators/architecture-scanner.js +18 -156
  245. package/src/lib/generators/handoff-generator.js +37 -112
  246. package/src/lib/generators/scaffold-generator.js +5 -101
  247. package/src/lib/generators/settings-generator.js +11 -122
  248. package/src/lib/git/branch-classifier.js +11 -84
  249. package/src/lib/git/default-branch.js +14 -169
  250. package/src/lib/git/evidence-staleness.js +2 -1
  251. package/src/lib/git/gh.js +2 -17
  252. package/src/lib/git/pending-prs.js +6 -37
  253. package/src/lib/git/pr-files.js +3 -28
  254. package/src/lib/git/pr-index.js +3 -34
  255. package/src/lib/git/tree-fingerprint.js +17 -17
  256. package/src/lib/git/tree-state.js +6 -33
  257. package/src/lib/graph/god-nodes.js +2 -22
  258. package/src/lib/graph/graph-health.js +3 -24
  259. package/src/lib/graph/graph-loader.js +5 -56
  260. package/src/lib/graph/graphify-runtime.js +7 -37
  261. package/src/lib/graph/graphifyignore.js +8 -74
  262. package/src/lib/graph/sanitize.js +16 -136
  263. package/src/lib/heavy/cleanup.js +2 -6
  264. package/src/lib/installers/effects.js +8 -93
  265. package/src/lib/installers/mcp-installer.js +9 -169
  266. package/src/lib/installers/memory-installer.js +8 -33
  267. package/src/lib/installers/templates-reconcile.js +5 -60
  268. package/src/lib/mutation/commands.js +3 -2
  269. package/src/lib/mutation/dotnet-introspection.js +281 -0
  270. package/src/lib/mutation/plan.js +14 -6
  271. package/src/lib/mutation/runner.js +1 -1
  272. package/src/lib/mutation/subset.js +47 -13
  273. package/src/lib/plan/artifact-vocabulary.js +40 -0
  274. package/src/lib/plan/checks.js +208 -0
  275. package/src/lib/plan/plan-parser.js +204 -0
  276. package/src/lib/plan/plan-template.js +18 -0
  277. package/src/lib/projects/registry.js +7 -44
  278. package/src/lib/review-reports.js +204 -0
  279. package/src/lib/risk-detector.js +131 -274
  280. package/src/lib/specs/adr-index.js +38 -85
  281. package/src/lib/specs/design-seed.js +18 -0
  282. package/src/lib/specs/frontmatter.js +31 -0
  283. package/src/lib/specs/gfm-table.js +11 -0
  284. package/src/lib/specs/revocations.js +31 -102
  285. package/src/lib/specs/screen-checklist.js +56 -132
  286. package/src/lib/stack/dotnet-build-ignores.js +43 -0
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +260 -0
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +37 -0
  291. package/src/lib/standards/sections.js +47 -0
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +23 -179
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -313
  296. package/src/lib/tasks/test-runner.js +107 -447
  297. package/src/lib/telemetry/metrics.js +13 -103
  298. package/src/lib/telemetry/telemetry-reader.js +13 -238
  299. package/src/lib/telemetry/verify-event.js +4 -34
  300. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  301. package/src/lib/validators/code-budget/index.js +0 -7
  302. package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
  303. package/src/lib/validators/contracts/index.js +0 -6
  304. package/src/lib/validators/nextjs/index.js +0 -5
  305. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  306. package/src/lib/validators/packages/index.js +0 -6
  307. package/src/lib/validators/packages/package-validator.js +6 -56
  308. package/src/lib/validators/reuse/index.js +0 -8
  309. package/src/lib/validators/reuse/reuse-validator.js +24 -137
  310. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  311. package/src/lib/validators/traceability-check.js +54 -156
  312. package/src/lib/validators/validation-runner.js +20 -125
  313. package/src/lib/validators/verify-runner.js +152 -575
  314. package/src/lib/work-type-detector.js +5 -67
  315. package/src/lib/worktree/docker-audit.js +17 -137
  316. package/src/lib/worktree/env-allocator.js +10 -98
  317. package/src/lib/worktree/feature-truth.js +12 -115
  318. package/src/lib/worktree/finish-backup.js +32 -242
  319. package/src/lib/worktree/fleet-scanner.js +31 -126
  320. package/src/lib/worktree/ignored-triage.js +15 -91
  321. package/src/lib/worktree/vscode-settings.js +2 -3
  322. package/src/lib/worktree/worktree-audit.js +21 -213
  323. package/src/lib/worktree/worktree-include.js +3 -5
  324. package/src/scripts/global-install.js +3 -9
  325. package/src/scripts/install-plugin.js +1 -13
  326. package/src/scripts/setup-infra.js +17 -138
  327. package/src/utils/agents-installer.js +92 -272
  328. package/src/utils/banner.js +4 -25
  329. package/src/utils/claude-md-injector.js +1 -37
  330. package/src/utils/claude-settings-manager.js +1 -87
  331. package/src/utils/file-copier.js +32 -90
  332. package/src/utils/hooks-installer.js +26 -296
  333. package/src/utils/skills-installer.js +4 -102
  334. package/src/utils/version-checker.js +4 -83
  335. package/framework/commands/morph-proposal.md +0 -57
  336. package/framework/evals/trace-schema.md +0 -67
  337. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  338. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  339. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  340. package/framework/hooks/shared/tasks-validator.js +0 -111
  341. package/framework/rules/morph-task-tracking.md +0 -54
  342. package/framework/rules/morph-workflow.md +0 -244
  343. package/framework/schemas/tasks.schema.json +0 -79
  344. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  345. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  346. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  347. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  348. package/framework/templates/docs/proposal.md +0 -192
  349. package/framework/templates/docs/spec.md +0 -57
  350. package/framework/templates/docs/ui-components.md +0 -52
  351. package/framework/templates/docs/ui-design-system.md +0 -76
  352. package/framework/templates/docs/ui-flows.md +0 -167
  353. package/framework/templates/examples/spec-examples.md +0 -91
  354. package/framework/templates/feature/decisions.md +0 -124
  355. package/framework/templates/feature/recap.md +0 -145
  356. package/framework/templates/tasks.template.json +0 -17
  357. package/src/commands/state/usecase.js +0 -143
  358. package/src/commands/tasks/dag.js +0 -181
  359. package/src/commands/utils/index.js +0 -3
  360. package/src/lib/agents/output-collector.js +0 -76
  361. package/src/lib/detectors/design-system-detector.js +0 -188
  362. package/src/lib/evaluator-report.js +0 -104
  363. package/src/lib/generators/design-system-generator.js +0 -508
  364. package/src/lib/specs/acervo-format.js +0 -70
  365. package/src/lib/specs/acervo-lifecycle.js +0 -264
  366. package/src/lib/specs/done-criteria-check.js +0 -200
  367. package/src/lib/specs/usecase-check.js +0 -301
  368. package/src/lib/specs/usecase-promoter.js +0 -301
  369. package/src/lib/specs/usecase-registry.js +0 -237
  370. package/src/lib/tasks/dispatch-planner.js +0 -319
  371. package/src/lib/tasks/dotnet-test-introspection.js +0 -632
  372. package/src/lib/tasks/mermaid-renderer.js +0 -132
  373. package/src/lib/tasks/task-classifier.js +0 -30
  374. package/src/lib/tasks/task-parser.js +0 -209
  375. package/src/lib/tasks/tasks-json-parser.js +0 -296
  376. package/src/lib/tasks/test-scope.js +0 -597
  377. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,41 +1,6 @@
1
- /**
2
- * graph-freshness.js — a regra ÚNICA de "o grafo desta árvore está em dia".
3
- *
4
- * Todo mundo que pergunta lê daqui: o refresh em background
5
- * (graph-refresh.js, decide se roda), o `graph status|explain|affected` e o
6
- * `doctor` (via src/lib/graph/graph-loader.js `getFreshness`) e o
7
- * `graph build|refresh` (grava o resultado). Mora em hooks/shared porque hook
8
- * não importa `src/`. Uma segunda cópia da regra é como o status dizia
9
- * "fresco" enquanto o refresh achava "velho", ou o contrário.
10
- *
11
- * Duas fontes, nesta ordem:
12
- * 1. o carimbo `built_at_commit` do graph.json contra o HEAD;
13
- * 2. o REGISTRO da última rodada, por árvore, em
14
- * `<git-common-dir>/morph-spec/graph-refresh-state.json`:
15
- * `{ [árvore]: { head, exit, ignoreHash?, at, unavailable?, seeded? } }`.
16
- *
17
- * O registro existe porque o `graphify update` do 0.9.50 NÃO recarimba quando
18
- * a topologia não muda — "No code-graph topology changes detected; outputs
19
- * left untouched" (watch.py): commit só de `.morph/`, corpo de método editado,
20
- * commit num arquivo excluído. O carimbo fica no commit antigo e, sem o
21
- * registro, o grafo parecia velho para sempre e cada disparo rodava o graphify
22
- * de novo (~80 s no MORPH_GHLBrain). Rodada com exit 0 no HEAD H vale como
23
- * grafo em dia em H: o 0.9.50 sai 1 quando recusa escrever (`_check_shrink`,
24
- * grafo ilegível), e "untouched" sai 0.
25
- *
26
- * As regras do `.graphifyignore` entram por CONTEÚDO, nunca pelo relógio: o
27
- * registro guarda o hash das regras sob as quais o grafo EM DISCO foi
28
- * extraído (uma rodada que falhou não o muda), e um hash diferente do das
29
- * regras que valem hoje é grafo velho — numa worktree sem arquivo próprio,
30
- * as da raiz, que são as que o `graph refresh` usa ali. Comentário, linha
31
- * em branco, BOM e CRLF não contam — trocar o cabeçalho não pede refresh.
32
- * Grafo sem registro das regras (anterior a este estado) com regras valendo é
33
- * `rules-unknown`: um refresh grava o registro e encerra.
34
- *
35
- * Fail-open: nada aqui lança.
36
- *
37
- * @module graph-freshness
38
- */
1
+ // Regra ÚNICA de "grafo em dia". O registro da rodada existe porque o `graphify
2
+ // update` não recarimba `built_at_commit` quando a topologia não muda; as regras
3
+ // do ignore entram por CONTEÚDO, nunca pelo relógio. Fail-open: nada aqui lança.
39
4
 
40
5
  import { createHash } from 'crypto';
41
6
  import { execFileSync } from 'child_process';
@@ -50,19 +15,12 @@ export const GRAPH_DIR = 'graphify-out';
50
15
  export const GRAPHIFY_IGNORE = '.graphifyignore';
51
16
  const STATE_FILE = 'graph-refresh-state.json';
52
17
 
53
- /** @param {string} tree */
54
18
  export function graphFile(tree) {
55
19
  return join(tree, GRAPH_DIR, 'graph.json');
56
20
  }
57
21
 
58
- /**
59
- * `built_at_commit` do graph.json. O graphify o grava como a ÚLTIMA chave, então
60
- * a leitura do fim do arquivo evita parsear 18 MB; a leitura inteira fica de
61
- * reserva para qualquer outra forma.
62
- *
63
- * @param {string} tree
64
- * @returns {string|null}
65
- */
22
+ // O graphify grava `built_at_commit` como a ÚLTIMA chave: ler o fim evita parsear
23
+ // o grafo inteiro, que fica de reserva para outra forma.
66
24
  export function readBuiltAtCommit(tree) {
67
25
  const file = graphFile(tree);
68
26
  let fd;
@@ -86,10 +44,6 @@ export function readBuiltAtCommit(tree) {
86
44
  }
87
45
  }
88
46
 
89
- /**
90
- * @param {string} tree
91
- * @returns {string|null}
92
- */
93
47
  export function readHead(tree) {
94
48
  try {
95
49
  return execFileSync('git', ['rev-parse', 'HEAD'], {
@@ -100,14 +54,7 @@ export function readHead(tree) {
100
54
  }
101
55
  }
102
56
 
103
- /**
104
- * As REGRAS de um `.graphifyignore`, sem o que o graphify não lê: comentário,
105
- * linha em branco, espaço no fim, BOM e CRLF. A ordem fica — no gitignore ela
106
- * muda o resultado.
107
- *
108
- * @param {string} text
109
- * @returns {string}
110
- */
57
+ // A ordem fica: no gitignore ela muda o resultado.
111
58
  export function ignoreRules(text) {
112
59
  return String(text ?? '')
113
60
  .replace(/^\uFEFF/, '')
@@ -117,37 +64,22 @@ export function ignoreRules(text) {
117
64
  .join('\n');
118
65
  }
119
66
 
120
- /**
121
- * @param {string|null} text
122
- * @returns {string|null} null quando não há arquivo
123
- */
124
67
  export function rulesHash(text) {
125
68
  if (text === null || text === undefined) return null;
126
69
  return createHash('sha256').update(ignoreRules(text)).digest('hex').slice(0, 16);
127
70
  }
128
71
 
129
- /**
130
- * A árvore principal do repositório — a única que o refresh em background
131
- * toca. `null` fora de um repositório e quando o common dir não é o `.git` de
132
- * uma árvore (repositório bare, `--separate-git-dir`): aí não há raiz.
133
- *
134
- * @param {string} dir - qualquer diretório do repositório, raiz ou worktree
135
- * @returns {string|null}
136
- */
72
+ /** `null` também quando o common dir não é o `.git` de uma árvore (bare, `--separate-git-dir`). */
137
73
  export function primaryTree(dir) {
138
74
  const common = dir ? resolveGitCommonDir(dir) : null;
139
75
  if (!common || basename(common) !== '.git') return null;
140
76
  return dirname(common);
141
77
  }
142
78
 
143
- /**
144
- * 1ª linha do `.graphifyignore` que o `graph refresh` põe numa worktree só
145
- * durante a rodada. Comentário não muda as regras nem o hash, e é por ele que a
146
- * sobra de uma rodada cujo processo morreu no meio é reconhecida.
147
- */
79
+ // 1ª linha da cópia temporária: reconhece a sobra de uma rodada que morreu no
80
+ // meio sem mudar as regras nem o hash.
148
81
  export const TEMP_IGNORE_MARKER = '# morph-spec: cópia temporária do graph refresh, apagada ao fim da rodada';
149
82
 
150
- /** @param {string|null} text */
151
83
  export function isTemporaryIgnore(text) {
152
84
  return String(text ?? '').replace(/^/, '').split(/\r?\n/, 1)[0] === TEMP_IGNORE_MARKER;
153
85
  }
@@ -160,14 +92,8 @@ function readText(path) {
160
92
  }
161
93
  }
162
94
 
163
- /**
164
- * O `.graphifyignore` com que o graphify roda nesta árvore: o do checkout;
165
- * numa worktree sem um (ou só com a sobra temporária), o da raiz, que é o que o
166
- * `graph refresh` põe no lugar durante a rodada.
167
- *
168
- * @param {string} tree
169
- * @returns {string|null} null quando nenhum arquivo vale
170
- */
95
+ // Numa worktree sem arquivo próprio valem as regras da raiz: é o que o
96
+ // `graph refresh` põe no lugar durante a rodada.
171
97
  export function ignoreTextOf(tree) {
172
98
  const own = readText(join(tree, GRAPHIFY_IGNORE));
173
99
  if (own !== null && !isTemporaryIgnore(own)) return own;
@@ -177,37 +103,18 @@ export function ignoreTextOf(tree) {
177
103
  return inherited !== null && !isTemporaryIgnore(inherited) ? inherited : null;
178
104
  }
179
105
 
180
- /**
181
- * @param {string} tree
182
- * @returns {string|null} null quando nenhum `.graphifyignore` vale para a árvore
183
- */
184
106
  export function ignoreRulesHash(tree) {
185
107
  return rulesHash(ignoreTextOf(tree));
186
108
  }
187
109
 
188
- /**
189
- * As linhas de `git status --porcelain` de `tree` sem a da sobra do
190
- * `.graphifyignore` temporário: ela é só uma cópia das regras da raiz, e o
191
- * `?? .graphifyignore` que deixa fazia o `finish` e o
192
- * `worktree remove --if-clean` recusarem. Só lê — quem confere a árvore chama
193
- * isto antes das recusas, e `dropLeftoverIgnore` só depois delas.
194
- *
195
- * @param {string} tree
196
- * @param {string[]} lines
197
- * @returns {string[]}
198
- */
110
+ // Só lê: quem confere a árvore chama isto antes das recusas, e
111
+ // `dropLeftoverIgnore` só depois delas.
199
112
  export function withoutLeftoverIgnore(tree, lines) {
200
113
  const leftover = `?? ${GRAPHIFY_IGNORE}`;
201
114
  if (!lines.includes(leftover) || !isTemporaryIgnore(readText(join(tree, GRAPHIFY_IGNORE)))) return lines;
202
115
  return lines.filter((line) => line !== leftover);
203
116
  }
204
117
 
205
- /**
206
- * Apaga a sobra do `.graphifyignore` temporário, reconhecida pelo marcador.
207
- *
208
- * @param {string} tree
209
- * @returns {boolean} apagou
210
- */
211
118
  export function dropLeftoverIgnore(tree) {
212
119
  const path = join(tree, GRAPHIFY_IGNORE);
213
120
  if (!isTemporaryIgnore(readText(path))) return false;
@@ -219,21 +126,12 @@ export function dropLeftoverIgnore(tree) {
219
126
  }
220
127
  }
221
128
 
222
- /**
223
- * Chave de uma árvore no registro. No win32 o caminho não distingue caixa, e
224
- * `R:\x` e `r:\x` são a mesma árvore.
225
- *
226
- * @param {string} tree
227
- */
129
+ // No win32 o caminho não distingue caixa.
228
130
  export function treeKey(tree) {
229
131
  const path = resolve(tree);
230
132
  return process.platform === 'win32' ? path.toLowerCase() : path;
231
133
  }
232
134
 
233
- /**
234
- * @param {string} dir - qualquer diretório do repositório
235
- * @returns {string|null}
236
- */
237
135
  export function refreshStatePath(dir) {
238
136
  const common = dir ? resolveGitCommonDir(dir) : null;
239
137
  return common ? join(common, 'morph-spec', STATE_FILE) : null;
@@ -248,10 +146,7 @@ function readState(path) {
248
146
  }
249
147
  }
250
148
 
251
- /**
252
- * @param {string} tree
253
- * @returns {{head: string|null, exit: number|null, ignoreHash?: string|null, at: string, unavailable?: boolean, seeded?: boolean}|null}
254
- */
149
+ /** @returns {{head: string|null, exit: number|null, ignoreHash?: string|null, at: string, unavailable?: boolean, seeded?: boolean}|null} */
255
150
  export function readRefreshRecord(tree) {
256
151
  const path = refreshStatePath(tree);
257
152
  if (!path) return null;
@@ -263,15 +158,7 @@ function pause(ms) {
263
158
  try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch { /* sem espera */ }
264
159
  }
265
160
 
266
- /**
267
- * Grava o registro de `tree` (substitui o anterior). Por tmp + rename: quem lê
268
- * no meio vê o arquivo velho ou o novo, nunca meio. As árvores que não existem
269
- * mais saem na mesma escrita — uma worktree removida não deixa lixo.
270
- *
271
- * @param {string} tree
272
- * @param {object} record
273
- * @returns {object|null} o registro gravado, ou null (sem git, disco)
274
- */
161
+ // As árvores que não existem mais saem na mesma escrita.
275
162
  export function writeRefreshRecord(tree, record) {
276
163
  const path = refreshStatePath(tree);
277
164
  if (!path) return null;
@@ -286,8 +173,7 @@ export function writeRefreshRecord(tree, record) {
286
173
  }
287
174
  state[key] = entry;
288
175
  writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
289
- // No win32 o rename sobre um arquivo aberto por outro leitor falha por um
290
- // instante (EPERM/EBUSY); o registro vale umas tentativas.
176
+ // No win32 o rename sobre um arquivo aberto por outro leitor falha por um instante.
291
177
  for (let attempt = 0; ; attempt++) {
292
178
  try {
293
179
  renameSync(tmp, path);
@@ -303,15 +189,8 @@ export function writeRefreshRecord(tree, record) {
303
189
  }
304
190
  }
305
191
 
306
- /**
307
- * Registra uma rodada do graphify sobre `tree`. As regras só mudam com exit 0:
308
- * uma rodada que falhou não mexeu no grafo, e o hash descreve o grafo em
309
- * disco, não a tentativa.
310
- *
311
- * @param {string} tree
312
- * @param {{head: string|null, exit: number|null, ignoreHash: string|null, unavailable?: boolean}} run
313
- * `head` e `ignoreHash` lidos ANTES da rodada — é o que o graphify leu
314
- */
192
+ // O hash das regras só muda com exit 0: descreve o grafo em disco, não a
193
+ // tentativa. `head` e `ignoreHash` são lidos ANTES da rodada.
315
194
  export function recordRefreshRun(tree, { head, exit, ignoreHash, unavailable = false }) {
316
195
  const previous = readRefreshRecord(tree);
317
196
  let rules = {};
@@ -325,23 +204,7 @@ export function recordRefreshRun(tree, { head, exit, ignoreHash, unavailable = f
325
204
  });
326
205
  }
327
206
 
328
- /**
329
- * O frescor do grafo de `tree`.
330
- *
331
- * missing sem graph.json
332
- * unknown sem git, ou sem carimbo nem registro que diga o commit
333
- * stale atrás do HEAD (`reason` ausente), ou em dia no commit mas com as
334
- * regras do ignore mudadas (`ignore-changed`) ou desconhecidas
335
- * (`rules-unknown`)
336
- * fresh carimbo = HEAD, ou rodada com exit 0 neste HEAD — e as regras
337
- * que valem para a árvore são as do grafo (`ignoreTextOf`; nenhum
338
- * arquivo valendo não afirma nada)
339
- *
340
- * @param {string} tree - o diretório que contém `graphify-out/`
341
- * @param {{graph?: object, head?: string|null}} [known] o que o chamador já
342
- * leu: o grafo parseado (o carimbo sai dele) e o HEAD
343
- * @returns {{state: 'missing'|'unknown'|'stale'|'fresh', built: string|null, head: string|null, reason?: 'ignore-changed'|'rules-unknown', record: object|null}}
344
- */
207
+ /** @returns {{state: 'missing'|'unknown'|'stale'|'fresh', built: string|null, head: string|null, reason?: 'ignore-changed'|'rules-unknown', record: object|null}} */
345
208
  export function graphFreshness(tree, { graph, head } = {}) {
346
209
  if (!graph && !existsSync(graphFile(tree))) {
347
210
  return { state: 'missing', built: null, head: null, record: null };
@@ -363,15 +226,8 @@ export function graphFreshness(tree, { graph, head } = {}) {
363
226
  return { state: 'fresh', ...base };
364
227
  }
365
228
 
366
- /**
367
- * Um refresh em background resolveria? Grafo velho, e sem uma rodada que já
368
- * FALHOU neste mesmo HEAD — essa só se repete quando o HEAD andar (ou por
369
- * `morph-spec graph refresh`, que é explícito). "graphify ausente" não conta
370
- * como falha: instalado, ele roda no próximo disparo.
371
- *
372
- * @param {{state: string, head: string|null, record: object|null}} freshness
373
- * @returns {boolean}
374
- */
229
+ // Uma rodada que FALHOU neste HEAD só se repete quando o HEAD andar;
230
+ // "graphify ausente" não conta como falha.
375
231
  export function refreshPending(freshness) {
376
232
  if (freshness?.state !== 'stale') return false;
377
233
  const r = freshness.record;
@@ -1,44 +1,6 @@
1
- /**
2
- * graph-refresh.js — refresh em background do grafo da árvore PRINCIPAL.
3
- *
4
- * Quem dispara: o elo `graph-refresh-kick` (PostToolUse, depois de um comando
5
- * Bash ou PowerShell com git/gh/morph-spec) e o SessionStart
6
- * (`worktree-context`). Os dois chamam `kickGraphRefresh`, que só olha o disco
7
- * e solta este mesmo arquivo como processo DESTACADO (`--run <raiz>`): quem
8
- * dispara nunca espera o graphify, que custa ~80 s no MORPH_GHLBrain mesmo sem
9
- * mudança nenhuma.
10
- *
11
- * Só a raiz. O processo destacado segura o diretório em que roda, e no win32
12
- * um diretório em uso não se apaga: com o refresh de uma worktree correndo, o
13
- * `finish` e o `worktree remove` falhavam com "Permission denied" e deixavam a
14
- * worktree meio removida. Por isso o worker nasce com `cwd` no git common dir,
15
- * nunca recebe o caminho de uma worktree, e o `graphify update` roda só na
16
- * raiz — que nenhum `finish` remove. A worktree nasce com o grafo semeado da
17
- * raiz (`seedGraphFromRoot`) e, dali em diante, é atualizada por
18
- * `morph-spec graph refresh`, em primeiro plano.
19
- *
20
- * Por que não o hook git do próprio graphify (`graphify hook install`), medido
21
- * no 0.9.50:
22
- * - ele sai sem fazer nada numa worktree vinculada (compara `--git-dir` com
23
- * `--git-common-dir`);
24
- * - não instala `post-merge`: merge, fast-forward e `git pull` — o caminho
25
- * pelo qual toda feature volta para a raiz — nunca rebuildam;
26
- * - o rebuild dele só re-extrai `git diff HEAD~1 HEAD`; um commit depois de
27
- * um fast-forward carimba `built_at_commit` = HEAD num grafo SEM os
28
- * arquivos que o merge trouxe — "fresh" mentiroso.
29
- * Aqui o critério é o da regra única (graph-freshness.js): o carimbo e o
30
- * registro da última rodada contra o HEAD, e as regras do ignore por conteúdo.
31
- *
32
- * Um lock por REPOSITÓRIO (no git common dir) serializa o refresh em
33
- * background com o `graph build|refresh` manual — cada `graphify update` usa
34
- * todos os núcleos e os dois escreveriam o mesmo graph.json. O dono renova o
35
- * carimbo do lock antes de cada rodada, e a checagem é refeita depois de
36
- * soltá-lo, para não perder o commit que chegou enquanto ele era segurado.
37
- *
38
- * Fail-open em tudo: sem git, sem graphify ou sem grafo, nada acontece.
39
- *
40
- * @module graph-refresh
41
- */
1
+ // Só a raiz: o worker destacado nasce com `cwd` no git common dir, porque no win32
2
+ // um diretório em uso não se apaga e o `finish` de uma worktree falhava no meio.
3
+ // A worktree anda por `morph-spec graph refresh`, em primeiro plano.
42
4
 
43
5
  import { spawn, spawnSync } from 'child_process';
44
6
  import {
@@ -58,14 +20,8 @@ import {
58
20
 
59
21
  export { primaryTree };
60
22
 
61
- /**
62
- * O que uma worktree herda do grafo da raiz. Fora da lista, de propósito:
63
- * `.graphify_root` guarda o caminho ABSOLUTO da raiz, e `cache/` é indexado
64
- * pelo mtime da árvore de origem — medido: copiado, não poupou re-extração.
65
- * O `.graphifyignore` também não vem: a worktree lê o do próprio checkout, e
66
- * pôr `/.graphifyignore` no exclude do common dir escondia também o da RAIZ
67
- * ainda não versionado (o `git add` dele passava a ser recusado).
68
- */
23
+ // Ficam fora de propósito: `.graphify_root` guarda o caminho absoluto da raiz, `cache/`
24
+ // é indexado pelo mtime da origem, e excluir `.graphifyignore` esconderia o da raiz.
69
25
  const SEED_FILES = ['manifest.json', '.graphify_analysis.json', '.graphify_labels.json', 'graph.json'];
70
26
 
71
27
  const EXCLUDE_MARKER = '# morph-spec: codebase graph inside worktrees (managed — see graph-refresh)';
@@ -77,10 +33,6 @@ const GRAPHIFY_TIMEOUT_MS = 20 * 60 * 1000;
77
33
  const MAX_ROUNDS = 3;
78
34
  const LOG_MAX_BYTES = 256 * 1024;
79
35
 
80
- /**
81
- * @param {string} dir
82
- * @returns {boolean} `dir` está na árvore principal (o refresh automático a cobre)
83
- */
84
36
  export function isPrimaryTree(dir) {
85
37
  const top = dir ? workTreeTop(dir) : null;
86
38
  const primary = primaryTree(dir);
@@ -92,7 +44,6 @@ function stateDir(dir) {
92
44
  return common ? join(common, 'morph-spec') : null;
93
45
  }
94
46
 
95
- /** @param {string} dir */
96
47
  export function refreshLockPath(dir) {
97
48
  const d = stateDir(dir);
98
49
  return d ? join(d, 'graph-refresh.lock') : null;
@@ -111,16 +62,8 @@ function mtimeOf(path) {
111
62
  }
112
63
  }
113
64
 
114
- /**
115
- * Estado do lock do repositório — o que `graph status` mostra como "refresh em
116
- * andamento". A idade conta da última renovação: um lock de processo morto,
117
- * ou sem renovação há mais que o teto, é `live: false`, e o próximo refresh o
118
- * recupera.
119
- *
120
- * @param {string} dir
121
- * @param {{now?: number, isAlive?: (pid: number) => boolean}} [deps]
122
- * @returns {{pid: number, since: string, renewed?: string, live: boolean}|null}
123
- */
65
+ // A idade conta da última renovação: processo morto ou sem renovação além do
66
+ // teto é `live: false`, e o próximo refresh recupera o lock.
124
67
  export function readRefreshLock(dir, { now = Date.now(), isAlive = pidAlive } = {}) {
125
68
  const path = refreshLockPath(dir);
126
69
  if (!path) return null;
@@ -133,11 +76,6 @@ export function readRefreshLock(dir, { now = Date.now(), isAlive = pidAlive } =
133
76
  }
134
77
  }
135
78
 
136
- /**
137
- * @param {string} dir
138
- * @param {{isAlive?: (pid: number) => boolean}} [deps]
139
- * @returns {string|null} o caminho do lock adquirido, ou null (ocupado ou sem git)
140
- */
141
79
  export function acquireRefreshLock(dir, deps = {}) {
142
80
  const path = refreshLockPath(dir);
143
81
  if (!path) return null;
@@ -162,13 +100,8 @@ export function acquireRefreshLock(dir, deps = {}) {
162
100
  return null;
163
101
  }
164
102
 
165
- /**
166
- * Renova o carimbo do lock, só se ainda for deste processo. Chamado antes de
167
- * cada rodada do graphify: uma posse que atravessa várias rodadas passaria do
168
- * teto de idade e seria roubada no meio de uma escrita.
169
- *
170
- * @param {string|null} path
171
- */
103
+ // Uma posse que atravessa várias rodadas passaria do teto de idade e seria
104
+ // roubada no meio de uma escrita.
172
105
  export function renewRefreshLock(path) {
173
106
  if (!path) return;
174
107
  try {
@@ -178,7 +111,6 @@ export function renewRefreshLock(path) {
178
111
  } catch { /* solto por outro — o release confere de novo */ }
179
112
  }
180
113
 
181
- /** Solta o lock só se ainda for deste processo. */
182
114
  export function releaseRefreshLock(path) {
183
115
  if (!path) return;
184
116
  try {
@@ -199,13 +131,7 @@ function appendRefreshLog(dir, entry) {
199
131
  } catch { /* log é diagnóstico, nunca bloqueia */ }
200
132
  }
201
133
 
202
- /**
203
- * `graphify` no PATH. No win32 o libuv só acha `.exe` sem shell; `.cmd`/`.bat`
204
- * (o que um shim ou um dublê de teste instala) passa pelo shell.
205
- *
206
- * @param {NodeJS.ProcessEnv} [env]
207
- * @returns {{file: string, shell: boolean}|null}
208
- */
134
+ // No win32 o libuv só acha `.exe` sem shell; `.cmd`/`.bat` passa pelo shell.
209
135
  export function resolveGraphify(env = process.env) {
210
136
  const names = process.platform === 'win32'
211
137
  ? ['graphify.exe', 'graphify.cmd', 'graphify.bat']
@@ -214,23 +140,13 @@ export function resolveGraphify(env = process.env) {
214
140
  return file ? { file, shell: /\.(cmd|bat)$/i.test(file) } : null;
215
141
  }
216
142
 
217
- /**
218
- * `graphify <args>` síncrono, com `cwd` em `tree`. É o runner do refresh em
219
- * background E do `graph build|refresh` manual: o mesmo binário resolvido e o
220
- * mesmo `PYTHONHASHSEED=0` — é o que o hook do próprio graphify fixa para o
221
- * clustering não trocar as comunidades de lugar a cada rodada.
222
- *
223
- * @param {string} tree
224
- * @param {string[]} args
225
- * @param {{env?: NodeJS.ProcessEnv, timeout?: number|null}} [opts] `timeout: null` = sem teto
226
- * @returns {{status: number|null, output: string, unavailable?: boolean, error?: string}}
227
- */
143
+ // `PYTHONHASHSEED=0` impede o clustering de trocar as comunidades de lugar a cada
144
+ // rodada. `timeout: null` = sem teto.
228
145
  export function runGraphify(tree, args, { env = process.env, timeout = GRAPHIFY_TIMEOUT_MS } = {}) {
229
146
  const bin = resolveGraphify(env);
230
147
  if (!bin) return { status: null, unavailable: true, output: '' };
231
- // Nunca com cwd na árvore: o graphify lê o alvo pelo argumento (o ignore e o
232
- // git dele, medido no 0.9.50), e um graphify órfão com cwd numa worktree a
233
- // prende no win32 até terminar — o finish e o worktree remove falhavam.
148
+ // Nunca com cwd na árvore: o graphify lê o alvo pelo argumento, e um órfão com
149
+ // cwd numa worktree a prende no win32 até terminar.
234
150
  const opts = {
235
151
  cwd: resolveGitCommonDir(tree) ?? tree,
236
152
  env: { ...env, PYTHONHASHSEED: '0' },
@@ -247,20 +163,8 @@ export function runGraphify(tree, args, { env = process.env, timeout = GRAPHIFY_
247
163
  return { status: r.status, output, ...(r.error ? { error: r.error.code } : {}) };
248
164
  }
249
165
 
250
- /**
251
- * Semeia uma worktree com o grafo da raiz. A worktree nasce de um commit da
252
- * raiz, então o grafo copiado ou já é o do HEAD dela ou fica a um
253
- * `morph-spec graph refresh` de distância — nunca a uma extração do zero.
254
- *
255
- * Só `graphify-out/` (no exclude do common dir, senão a worktree fica suja
256
- * para o `finish`). O registro da worktree copia o que se SABE do grafo da
257
- * raiz: o commit em que ele está em dia e as regras com que foi extraído —
258
- * nada inventado. Raiz sem registro das regras deixa a semente sem elas.
259
- *
260
- * @param {string} tree
261
- * @param {string} root
262
- * @returns {{seeded: boolean, reason?: string}}
263
- */
166
+ // O registro da semente copia só o que se SABE do grafo da raiz; `graphify-out/`
167
+ // vai para o exclude do common dir, senão a worktree fica suja para o `finish`.
264
168
  export function seedGraphFromRoot(tree, root) {
265
169
  if (!tree || !root || treeKey(tree) === treeKey(root)) return { seeded: false, reason: 'is-root' };
266
170
  if (existsSync(graphFile(tree))) return { seeded: false, reason: 'present' };
@@ -272,8 +176,7 @@ export function seedGraphFromRoot(tree, root) {
272
176
  for (const name of SEED_FILES) {
273
177
  const src = join(root, GRAPH_DIR, name);
274
178
  if (!existsSync(src)) continue;
275
- // Por rename, e o graph.json por último: a presença dele é o sinal de
276
- // semeadura completa, e quem lê no meio nunca vê meio arquivo.
179
+ // graph.json por último: a presença dele é o sinal de semeadura completa.
277
180
  const tmp = join(dst, `${name}.seed.${process.pid}`);
278
181
  copyFileSync(src, tmp);
279
182
  renameSync(tmp, join(dst, name));
@@ -294,13 +197,6 @@ export function seedGraphFromRoot(tree, root) {
294
197
  }
295
198
  }
296
199
 
297
- /**
298
- * Semeia a árvore de `dir` a partir da raiz, se ela for uma worktree sem
299
- * grafo — o que o SessionStart faz por uma worktree criada antes da semeadura
300
- * existir.
301
- *
302
- * @param {string} dir
303
- */
304
200
  export function seedFromPrimary(dir) {
305
201
  const tree = dir ? workTreeTop(dir) : null;
306
202
  const root = primaryTree(dir);
@@ -308,17 +204,8 @@ export function seedFromPrimary(dir) {
308
204
  return seedGraphFromRoot(tree, root);
309
205
  }
310
206
 
311
- /**
312
- * O trabalho do processo destacado, em primeiro plano (é o que os testes
313
- * chamam). Roda `graphify update` na raiz do repositório de `dir` enquanto o
314
- * grafo dela precisar (`refreshPending`), até `MAX_ROUNDS` rodadas — um
315
- * commit que chega no meio pede outra. Cada rodada vai para o registro
316
- * (graph-freshness.js) e para o log.
317
- *
318
- * @param {string} dir - qualquer diretório do repositório; o alvo é sempre a raiz
319
- * @param {{env?: NodeJS.ProcessEnv, runUpdate?: (tree: string) => {status: number|null, unavailable?: boolean, output?: string}}} [deps]
320
- * @returns {{ran: boolean, locked?: boolean, reason?: string, results: object[]}}
321
- */
207
+ // A checagem é refeita depois de soltar o lock: um commit que chegou enquanto
208
+ // ele era segurado pede outra rodada.
322
209
  export function runRefresh(dir, { env = process.env, runUpdate = (tree) => runGraphify(tree, ['update', tree], { env }) } = {}) {
323
210
  const root = primaryTree(dir);
324
211
  if (!root) return { ran: false, reason: 'no-primary-tree', results: [] };
@@ -357,23 +244,8 @@ export function runRefresh(dir, { env = process.env, runUpdate = (tree) => runGr
357
244
  return { ran: results.length > 0, results };
358
245
  }
359
246
 
360
- /**
361
- * Dispara o refresh da RAIZ em background e volta na hora — de qualquer
362
- * árvore do repositório. Só olha o disco, sem subprocesso: sem grafo na raiz
363
- * não há o que refrescar; grafo que não pede refresh no HEAD lido do disco não
364
- * paga um processo por comando git; com o graphify registrado como ausente e
365
- * ainda fora do PATH, não há quem rode. HEAD que o disco não responde (reftable,
366
- * branch sem commit) fica para o worker, que confere de novo sob o lock.
367
- *
368
- * O worker nasce com `cwd` no git common dir e só recebe a raiz: nenhum
369
- * processo destacado segura o diretório de uma worktree.
370
- *
371
- * `MORPH_GRAPH_AUTO_REFRESH=0` desliga (CI, máquina sem folga de CPU).
372
- *
373
- * @param {string} dir
374
- * @param {{spawnFn?: typeof spawn, env?: NodeJS.ProcessEnv}} [deps]
375
- * @returns {{kicked: boolean, reason?: string, pid?: number}}
376
- */
247
+ // Só olha o disco: roda a cada comando git do agente. HEAD que o disco não
248
+ // responde (reftable, branch sem commit) fica para o worker, sob o lock.
377
249
  export function kickGraphRefresh(dir, { spawnFn = spawn, env = process.env } = {}) {
378
250
  if (env.MORPH_GRAPH_AUTO_REFRESH === '0') return { kicked: false, reason: 'disabled' };
379
251
  const root = primaryTree(dir);
@@ -95,13 +95,8 @@ function kindOfTokens(tokens) {
95
95
  return null;
96
96
  }
97
97
 
98
- /**
99
- * The first heavy command of a Bash command line, as a label, or null. Looks at the executable of
100
- * each `;`/`&&`/`||`/`|`/newline segment, so `morph-spec heavy run -- dotnet test` and `echo "dotnet build"`
101
- * are not heavy: the first runs under its own slot, the second runs nothing.
102
- * @param {string} command
103
- * @returns {string|null}
104
- */
98
+ // Judges only each segment's executable: `morph-spec heavy run -- dotnet test` already holds its
99
+ // own slot, and `echo "dotnet build"` runs nothing.
105
100
  export function heavyKind(command) {
106
101
  for (const segment of splitSegments(command)) {
107
102
  const tokens = skipWrappers(tokenize(segment.replace(/^[\s({]+/, '').replace(/[\s)}]+$/, '')));
@@ -111,11 +106,8 @@ export function heavyKind(command) {
111
106
  return null;
112
107
  }
113
108
 
114
- /**
115
- * How long a foreground Bash call can run: its own `timeout` (capped at the platform maximum) plus slack.
116
- * The lease has no live process to watch, so this is the only thing that frees it after a denied or
117
- * interrupted call.
118
- */
109
+ // The lease has no live process to watch: this TTL is the only thing that frees it after a
110
+ // denied or interrupted call.
119
111
  const BASH_DEFAULT_TIMEOUT_MS = 2 * 60_000;
120
112
  const BASH_MAX_TIMEOUT_MS = 10 * 60_000;
121
113
  const LEASE_SLACK_MS = 30_000;