@polymorphism-tech/morph-spec 9.0.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +11 -63
  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 +2 -1
  9. package/framework/MORPH.md +3 -3
  10. package/framework/agents.json +19 -11
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +22 -31
  14. package/framework/evals/review-checklist.md +12 -2
  15. package/framework/hooks/README.md +8 -44
  16. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  17. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  18. package/framework/hooks/claude-code/core/chain.js +4 -65
  19. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  20. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  21. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  22. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  23. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  24. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  25. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  26. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  27. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  28. package/framework/hooks/claude-code/core/state-sync.js +12 -68
  29. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  30. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  31. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  32. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  33. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  34. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  35. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  36. package/framework/hooks/claude-code/statusline.sh +11 -11
  37. package/framework/hooks/shared/activity-logger.js +9 -89
  38. package/framework/hooks/shared/agent-reap.js +2 -17
  39. package/framework/hooks/shared/build-check.js +6 -75
  40. package/framework/hooks/shared/claim-registry.js +9 -119
  41. package/framework/hooks/shared/command-tokens.js +6 -51
  42. package/framework/hooks/shared/comment-rules.js +5 -20
  43. package/framework/hooks/shared/comment-scanner.js +6 -25
  44. package/framework/hooks/shared/comment-stripper.js +11 -85
  45. package/framework/hooks/shared/e2e-check.js +0 -0
  46. package/framework/hooks/shared/errata.js +2 -15
  47. package/framework/hooks/shared/feature-locator.js +11 -95
  48. package/framework/hooks/shared/frontend-locations.js +1 -3
  49. package/framework/hooks/shared/gate-question.js +1 -7
  50. package/framework/hooks/shared/git-exclude.js +3 -20
  51. package/framework/hooks/shared/git-paths.js +8 -104
  52. package/framework/hooks/shared/graph-freshness.js +22 -166
  53. package/framework/hooks/shared/graph-refresh.js +21 -149
  54. package/framework/hooks/shared/heavy-command.js +4 -12
  55. package/framework/hooks/shared/hook-response.js +8 -83
  56. package/framework/hooks/shared/morph-cli.js +8 -52
  57. package/framework/hooks/shared/payload-utils.js +10 -132
  58. package/framework/hooks/shared/phase-utils.js +1 -42
  59. package/framework/hooks/shared/redact-secrets.js +4 -15
  60. package/framework/hooks/shared/reparse-scan.js +6 -66
  61. package/framework/hooks/shared/slice-progress.js +4 -23
  62. package/framework/hooks/shared/source-files.js +4 -17
  63. package/framework/hooks/shared/state-reader.js +13 -183
  64. package/framework/hooks/shared/state-writer.js +8 -52
  65. package/framework/hooks/shared/stdin-reader.js +1 -11
  66. package/framework/hooks/shared/telemetry-logger.js +7 -67
  67. package/framework/hooks/shared/transcript-usage.js +7 -29
  68. package/framework/hooks/shared/worktree-root.js +7 -29
  69. package/framework/rules/csharp-standards.md +1 -1
  70. package/framework/rules/frontend-standards.md +1 -0
  71. package/framework/rules/hangfire-standards.md +1 -1
  72. package/framework/rules/neon-standards.md +1 -1
  73. package/framework/rules/nextjs-standards.md +1 -1
  74. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  75. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  76. package/framework/skills/core/morph-apply/SKILL.md +4 -4
  77. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  78. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  79. package/framework/skills/core/morph-media/SKILL.md +1 -1
  80. package/framework/skills/core/morph-plan/SKILL.md +9 -7
  81. package/framework/skills/core/morph-review/SKILL.md +9 -6
  82. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  83. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  84. package/framework/standards/STANDARDS.json +36 -14
  85. package/framework/standards/STANDARDS.meta.json +16 -2
  86. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  87. package/framework/standards/ai-agents/agent-session.md +1 -1
  88. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  89. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  90. package/framework/standards/ai-agents/guardrails.md +1 -1
  91. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  92. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  93. package/framework/standards/ai-agents/production.md +1 -1
  94. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  95. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  96. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  97. package/framework/standards/ai-agents/setup.md +2 -2
  98. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  99. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  100. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  101. package/framework/standards/ai-agents/workflows.md +1 -1
  102. package/framework/standards/architecture/code-comments.md +1 -1
  103. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  104. package/framework/standards/backend/api/validation.md +1 -1
  105. package/framework/standards/backend/dotnet/testing.md +10 -4
  106. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  107. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  108. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  109. package/framework/standards/data/nosql/cache/redis.md +1 -1
  110. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  111. package/framework/standards/frontend/admin-app/forms.md +1 -1
  112. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  113. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  114. package/framework/standards/frontend/nextjs/testing.md +10 -6
  115. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  116. package/framework/templates/.idea/morph-templates.xml +92 -0
  117. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  118. package/framework/templates/CLAUDE.md.core.template +3 -3
  119. package/framework/templates/IDE-SNIPPETS.md +262 -262
  120. package/framework/templates/REGISTRY.json +9 -9
  121. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  122. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  123. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  124. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  125. package/framework/templates/docs/plan.md +3 -1
  126. package/framework/templates/docs/ui-mockups.md +4 -0
  127. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  128. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  129. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  130. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  131. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  132. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  133. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  134. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  135. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  136. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  137. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  138. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  139. package/framework/templates/state.template.json +17 -17
  140. package/framework/templates/ui/design-system.css +226 -226
  141. package/package.json +1 -1
  142. package/src/commands/context/context-refresh.js +0 -16
  143. package/src/commands/dashboard/dashboard.js +2 -8
  144. package/src/commands/e2e/e2e.js +17 -104
  145. package/src/commands/graph/affected.js +0 -14
  146. package/src/commands/graph/build.js +11 -50
  147. package/src/commands/graph/explain.js +0 -16
  148. package/src/commands/graph/status.js +1 -12
  149. package/src/commands/mcp/mcp-setup.js +4 -62
  150. package/src/commands/observability/cost.js +5 -57
  151. package/src/commands/observability/telemetry.js +1 -14
  152. package/src/commands/project/branch.js +4 -45
  153. package/src/commands/project/claim.js +4 -48
  154. package/src/commands/project/doctor.js +48 -302
  155. package/src/commands/project/env.js +1 -27
  156. package/src/commands/project/fleet.js +3 -32
  157. package/src/commands/project/init.js +4 -68
  158. package/src/commands/project/install-plugin-cmd.js +0 -1
  159. package/src/commands/project/standards.js +1 -62
  160. package/src/commands/project/status.js +0 -53
  161. package/src/commands/project/update.js +31 -239
  162. package/src/commands/project/worktree.js +108 -882
  163. package/src/commands/scaffold/scaffold.js +0 -25
  164. package/src/commands/state/advance.js +14 -85
  165. package/src/commands/state/approve.js +15 -97
  166. package/src/commands/state/archive.js +12 -64
  167. package/src/commands/state/auto-approval.js +2 -5
  168. package/src/commands/state/create.js +22 -109
  169. package/src/commands/state/delete.js +11 -63
  170. package/src/commands/state/finish.js +98 -475
  171. package/src/commands/state/gate-check.js +22 -132
  172. package/src/commands/state/gate-decision.js +3 -39
  173. package/src/commands/state/handoff.js +3 -30
  174. package/src/commands/state/list.js +14 -84
  175. package/src/commands/state/report-check.js +8 -58
  176. package/src/commands/state/retype.js +4 -26
  177. package/src/commands/validation/index.js +0 -3
  178. package/src/commands/validation/mutate.js +11 -11
  179. package/src/commands/validation/plan-check.js +4 -2
  180. package/src/commands/validation/tests-audit.js +7 -0
  181. package/src/commands/validation/validate-feature.js +10 -97
  182. package/src/commands/validation/verify.js +30 -113
  183. package/src/core/paths/output-schema.js +2 -101
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +0 -27
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +22 -304
  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 +0 -1
  191. package/src/core/work-types.js +1 -36
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  194. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  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 +1 -5
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/main.jsx +13 -13
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  211. package/src/dashboard/app/pages/Features.jsx +1 -8
  212. package/src/dashboard/app/pages/Guide.jsx +0 -1
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  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 +10 -69
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +0 -6
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +10 -66
  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 +2 -6
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/dashboard/vite.config.js +24 -24
  233. package/src/lib/ai/ai-checks.js +7 -52
  234. package/src/lib/ai/ai-project-scan.js +14 -108
  235. package/src/lib/ai/code-audit.js +7 -63
  236. package/src/lib/ai/pin-drift.js +8 -51
  237. package/src/lib/ai/registry-audit.js +11 -90
  238. package/src/lib/ci/ci-verify.js +4 -19
  239. package/src/lib/ci/github-ci.js +9 -37
  240. package/src/lib/ci/workflow-scaffold.js +14 -10
  241. package/src/lib/detectors/claude-config-detector.js +13 -74
  242. package/src/lib/detectors/index.js +4 -50
  243. package/src/lib/e2e/applicability.js +37 -0
  244. package/src/lib/e2e/compose-env.js +5 -66
  245. package/src/lib/e2e/stack-teardown.js +6 -40
  246. package/src/lib/generators/architecture-scanner.js +18 -156
  247. package/src/lib/generators/handoff-generator.js +9 -53
  248. package/src/lib/generators/scaffold-generator.js +5 -101
  249. package/src/lib/generators/settings-generator.js +4 -37
  250. package/src/lib/git/branch-classifier.js +11 -84
  251. package/src/lib/git/default-branch.js +14 -169
  252. package/src/lib/git/evidence-staleness.js +1 -0
  253. package/src/lib/git/gh.js +2 -17
  254. package/src/lib/git/pending-prs.js +6 -37
  255. package/src/lib/git/pr-files.js +3 -28
  256. package/src/lib/git/pr-index.js +3 -34
  257. package/src/lib/git/tree-fingerprint.js +3 -15
  258. package/src/lib/git/tree-state.js +6 -33
  259. package/src/lib/graph/god-nodes.js +2 -22
  260. package/src/lib/graph/graph-health.js +3 -24
  261. package/src/lib/graph/graph-loader.js +5 -56
  262. package/src/lib/graph/graphify-runtime.js +7 -37
  263. package/src/lib/graph/graphifyignore.js +8 -74
  264. package/src/lib/graph/sanitize.js +16 -136
  265. package/src/lib/heavy/cleanup.js +1 -1
  266. package/src/lib/installers/effects.js +8 -93
  267. package/src/lib/installers/mcp-installer.js +9 -169
  268. package/src/lib/installers/memory-installer.js +4 -27
  269. package/src/lib/installers/templates-reconcile.js +5 -60
  270. package/src/lib/mutation/adapters.js +6 -17
  271. package/src/lib/mutation/dotnet-introspection.js +20 -106
  272. package/src/lib/mutation/runner.js +23 -75
  273. package/src/lib/mutation/subset.js +1 -4
  274. package/src/lib/mutation/trx.js +0 -1
  275. package/src/lib/plan/artifact-vocabulary.js +5 -25
  276. package/src/lib/plan/checks.js +34 -33
  277. package/src/lib/plan/plan-parser.js +7 -8
  278. package/src/lib/projects/registry.js +7 -44
  279. package/src/lib/review-reports.js +11 -26
  280. package/src/lib/risk-detector.js +41 -173
  281. package/src/lib/specs/adr-index.js +6 -34
  282. package/src/lib/specs/frontmatter.js +2 -15
  283. package/src/lib/specs/gfm-table.js +1 -7
  284. package/src/lib/specs/revocations.js +1 -14
  285. package/src/lib/specs/screen-checklist.js +47 -124
  286. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +1 -8
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +1 -7
  291. package/src/lib/standards/sections.js +2 -13
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +21 -169
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -240
  296. package/src/lib/tasks/test-runner.js +43 -341
  297. package/src/lib/telemetry/metrics.js +7 -36
  298. package/src/lib/telemetry/telemetry-reader.js +13 -125
  299. package/src/lib/telemetry/verify-event.js +2 -30
  300. package/src/lib/tests-audit/audit.js +96 -0
  301. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  302. package/src/lib/validators/code-budget/index.js +0 -7
  303. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  304. package/src/lib/validators/contracts/index.js +0 -6
  305. package/src/lib/validators/nextjs/index.js +0 -5
  306. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  307. package/src/lib/validators/packages/index.js +0 -6
  308. package/src/lib/validators/packages/package-validator.js +6 -56
  309. package/src/lib/validators/reuse/index.js +0 -8
  310. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  311. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  312. package/src/lib/validators/traceability-check.js +5 -63
  313. package/src/lib/validators/validation-runner.js +21 -158
  314. package/src/lib/validators/verify-runner.js +85 -376
  315. package/src/lib/work-type-detector.js +5 -67
  316. package/src/lib/worktree/docker-audit.js +17 -136
  317. package/src/lib/worktree/env-allocator.js +10 -98
  318. package/src/lib/worktree/feature-truth.js +12 -115
  319. package/src/lib/worktree/finish-backup.js +32 -242
  320. package/src/lib/worktree/fleet-scanner.js +13 -95
  321. package/src/lib/worktree/ignored-triage.js +15 -91
  322. package/src/lib/worktree/vscode-settings.js +2 -3
  323. package/src/lib/worktree/worktree-audit.js +21 -213
  324. package/src/lib/worktree/worktree-include.js +3 -5
  325. package/src/scripts/global-install.js +3 -9
  326. package/src/scripts/install-plugin.js +1 -13
  327. package/src/scripts/setup-infra.js +17 -147
  328. package/src/utils/agents-installer.js +29 -276
  329. package/src/utils/banner.js +4 -25
  330. package/src/utils/claude-md-injector.js +1 -37
  331. package/src/utils/claude-settings-manager.js +1 -52
  332. package/src/utils/file-copier.js +11 -95
  333. package/src/utils/hooks-installer.js +21 -298
  334. package/src/utils/skills-installer.js +4 -101
  335. package/src/utils/version-checker.js +4 -83
  336. package/framework/commands/morph-proposal.md +0 -9
  337. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  338. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  339. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  340. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
  341. package/src/commands/utils/index.js +0 -3
  342. package/src/lib/detectors/design-system-detector.js +0 -188
  343. package/src/lib/generators/design-system-generator.js +0 -508
  344. package/src/lib/specs/old-format.js +0 -68
  345. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,17 +1,6 @@
1
1
  /**
2
- * Many projects in one dashboard. Each project keeps its own live API
3
- * (project-api.js: features, activity, files), created on first use; this
4
- * module lists the projects, routes `/p/:id/…` to the right one, and answers
5
- * which project a changed file belongs to.
6
- *
7
- * The list is the registry (src/lib/projects/registry.js) plus the project the
8
- * dashboard was launched from, which is always shown even if it cannot be
9
- * registered. Projects found next to the registered ones are offered, never
10
- * added on their own.
11
- *
12
- * A cold project can take seconds to read (a 100k-event log). The portfolio
13
- * never waits for one: `summary` is served only for projects already in
14
- * memory, and the rest are read in the background (`warmNext`).
2
+ * The portfolio never waits for a cold project (a 100k-event log takes seconds):
3
+ * `summary` is served only from memory, the rest is read by `warmNext`.
15
4
  */
16
5
 
17
6
  import { dirname, basename } from 'path';
@@ -25,18 +14,12 @@ function slug(text) {
25
14
 
26
15
  const lower = (p) => (process.platform === 'win32' ? p.toLowerCase() : p);
27
16
 
28
- /**
29
- * @param {{ launchDir: string, registry?: object, createApi?: Function }} opts
30
- * `registry` and `createApi` are test seams.
31
- */
32
17
  export function createPortfolio({ launchDir, registry = registryModule, createApi = createProjectApi }) {
33
18
  const launchKey = registry.projectKey(launchDir);
34
- const apis = new Map(); // dir key -> project api
35
- // The last summary served per project: while a changed project is read
36
- // again, the portfolio keeps showing it instead of going back to "reading".
37
- const lastSummary = new Map(); // dir key -> summary
19
+ const apis = new Map();
20
+ // While a changed project is read again, its last summary keeps showing.
21
+ const lastSummary = new Map();
38
22
 
39
- /** The projects shown, with stable ids derived from the folder name. */
40
23
  function list() {
41
24
  const dirs = registry.readRegistry().projects.map((p) => ({ dir: p.dir, name: p.name, source: p.source }));
42
25
  if (!dirs.some((p) => lower(p.dir) === lower(launchKey))) {
@@ -61,7 +44,6 @@ export function createPortfolio({ launchDir, registry = registryModule, createAp
61
44
  return list().find((p) => p.id === id) || null;
62
45
  }
63
46
 
64
- /** The project a changed path belongs to — the deepest registered folder containing it. */
65
47
  function ownerOf(path) {
66
48
  const p = lower(String(path).replace(/\\/g, '/'));
67
49
  let best = null;
@@ -78,7 +60,6 @@ export function createPortfolio({ launchDir, registry = registryModule, createAp
78
60
  return registry.discoverProjects(roots).filter((s) => !projects.some((p) => lower(p.dir) === lower(s.dir)));
79
61
  }
80
62
 
81
- /** Read the next project that is not in memory yet. @returns {object|null} the project warmed */
82
63
  function warmNext() {
83
64
  const next = list().find((p) => !apiFor(p.dir).ready());
84
65
  if (!next) return null;
@@ -106,11 +87,6 @@ export function createPortfolio({ launchDir, registry = registryModule, createAp
106
87
 
107
88
  const json = (status, body) => ({ status, body });
108
89
 
109
- /**
110
- * @param {string} method
111
- * @param {string} url - relative to `/api/morph`
112
- * @param {object|null} [body] - parsed JSON of a POST
113
- */
114
90
  function handle(method, url, body = null) {
115
91
  const u = new URL(url, 'http://local');
116
92
  const parts = u.pathname.split('/').filter(Boolean);
@@ -1,11 +1,6 @@
1
1
  /**
2
- * The live half of the dashboard: the project's features and activity, served
3
- * as JSON by the Vite dev server (`/api/morph/*`) and refreshed when `.morph/`
4
- * changes on disk. The framework knowledge stays in the static virtual module.
5
- *
6
- * Two caches with two lifetimes. A running agent appends to `events.jsonl` on
7
- * every tool call, so a log change drops only the event cache — rescanning the
8
- * features (git calls for the active ones) on each tool call would be waste.
2
+ * Two caches with two lifetimes: an agent appends to `events.jsonl` on every tool
3
+ * call, so a log change drops only the event cache, never the feature scan.
9
4
  */
10
5
 
11
6
  import { join } from 'path';
@@ -16,17 +11,9 @@ import { containerDirs } from '../../../framework/hooks/shared/worktree-root.js'
16
11
  import { isFinished } from './feature-status.js';
17
12
  import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
18
13
 
19
- /** How long a remembered "which tree owns this feature" answer is trusted without a structural change. */
20
14
  const TREE_TTL_MS = 60_000;
21
15
 
22
- /**
23
- * Which cache a changed path invalidates, or null when it is none of ours.
24
- * `.morph/framework/` is the installed framework copy (often a junction), and
25
- * it never changes what this API returns.
26
- *
27
- * @param {string} path
28
- * @returns {'activity'|'features'|null}
29
- */
16
+ /** `.morph/framework/` is the installed framework copy and never changes what this API returns. */
30
17
  export function classifyChange(path) {
31
18
  const p = String(path).replace(/\\/g, '/');
32
19
  if (!p.includes('/.morph/') && !p.endsWith('/.morph')) return null;
@@ -36,19 +23,12 @@ export function classifyChange(path) {
36
23
  return null;
37
24
  }
38
25
 
39
- /**
40
- * A full-page reload Vite would send for a file under a project's `.morph/`
41
- * (an agent writing a design .html). The dashboard's own code never lives
42
- * there, so such a reload is never needed.
43
- */
26
+ /** Vite would full-reload for a `.html` under `.morph/`; the dashboard's code never lives there. */
44
27
  export function isMorphDataReload(path) {
45
28
  return typeof path === 'string' && path.replace(/\\/g, '/').includes('/.morph/');
46
29
  }
47
30
 
48
- /**
49
- * The directories worth watching: the root's and each worktree's feature,
50
- * archive, log and config folders — never a whole worktree (its node_modules).
51
- */
31
+ /** Never a whole worktree: its node_modules. */
52
32
  export function watchTargets(projectDir) {
53
33
  const morphDirs = [join(projectDir, '.morph')];
54
34
  for (const container of containerDirs(projectDir)) {
@@ -72,12 +52,6 @@ function json(status, body) {
72
52
  return { status, body };
73
53
  }
74
54
 
75
- /**
76
- * @param {string} projectDir
77
- * @param {{ scanFeatures?: Function, readFeatureDetail?: Function,
78
- * readFeatureFile?: Function, loadEvents?: Function, resolveTree?: Function,
79
- * now?: () => number }} [deps] - test seams
80
- */
81
55
  export function createProjectApi(projectDir, deps = {}) {
82
56
  const scan = deps.scanFeatures || scanFeatures;
83
57
  const detail = deps.readFeatureDetail || readFeatureDetail;
@@ -87,10 +61,8 @@ export function createProjectApi(projectDir, deps = {}) {
87
61
  : {});
88
62
  const cache = { features: null };
89
63
 
90
- // Which tree owns a feature costs a few git calls (~0.3 s each on Windows)
91
- // and only changes when a file or folder appears or disappears — never when
92
- // an agent rewrites feature.json or a slice report, which is what happens all
93
- // the time. Remembered per feature; a structural change or TTL_MS forgets it.
64
+ // Which tree owns a feature costs git calls (~0.3 s each on Windows) and only
65
+ // changes on add/remove, never on a rewrite: remembered until a structural event or TTL_MS.
94
66
  const resolveTree = deps.resolveTree || resolveFeatureTree;
95
67
  const now = deps.now || Date.now;
96
68
  const trees = new Map();
@@ -104,17 +76,9 @@ export function createProjectApi(projectDir, deps = {}) {
104
76
 
105
77
  const features = () => (cache.features ||= scan(projectDir, { resolveTree: rememberedTree }));
106
78
  const events = () => store.events();
107
- // Where the list scan found each feature: opening one reuses it instead of
108
- // asking git again.
109
79
  const known = (name) => features().features?.find((f) => f.name === name) || undefined;
110
80
 
111
- /**
112
- * @param {'features'|'activity'|'all'} scope
113
- * @param {string} [path] - the file that changed; a live log is then tailed
114
- * instead of reloading every log
115
- * @param {string} [event] - the watcher event; anything but 'change' (a file
116
- * or folder added or removed) may move a feature to another tree
117
- */
81
+ /** Any `event` but 'change' may move a feature to another tree. */
118
82
  function invalidate(scope, path, event) {
119
83
  if (scope === 'features' || scope === 'all') cache.features = null;
120
84
  if (scope === 'all' || (scope === 'features' && event !== 'change')) trees.clear();
@@ -122,10 +86,6 @@ export function createProjectApi(projectDir, deps = {}) {
122
86
  else if (scope === 'activity') store.changed(path);
123
87
  }
124
88
 
125
- /**
126
- * @param {string} url - path + query, relative to `/api/morph`
127
- * @returns {{ status: number, body: object }}
128
- */
129
89
  function handle(url) {
130
90
  const u = new URL(url, 'http://local');
131
91
  const parts = u.pathname.split('/').filter(Boolean).map(decodeURIComponent);
@@ -159,20 +119,14 @@ export function createProjectApi(projectDir, deps = {}) {
159
119
  }
160
120
  }
161
121
 
162
- /** Load everything once, so the first page a person opens does not pay for it. */
163
122
  function warm() {
164
123
  try { features(); events(); } catch { /* the first request will report it */ }
165
124
  }
166
125
 
167
- /** Cheap to answer now: the list and the events are both in memory. */
168
126
  function ready() {
169
127
  return cache.features !== null && store.ready();
170
128
  }
171
129
 
172
- /**
173
- * What the portfolio shows of this project: counts, the active features with
174
- * their last activity, and when anything last happened.
175
- */
176
130
  function summary() {
177
131
  const scan = features();
178
132
  const last = lastActivityByFeature(events());
@@ -20,8 +20,7 @@ function parseFrontmatter(content) {
20
20
 
21
21
  const ROUTE_LINE = /^- \*\*([a-z0-9-]+)\*\* \(`([^`]+\.md)`\)/;
22
22
 
23
- // Bullet form: - **<id>** (`<path under standards/>`) — <digest>. An `@path`
24
- // import inside .claude/rules resolves relative to the rule file and never loads.
23
+ // An `@path` import inside .claude/rules resolves relative to the rule file and never loads.
25
24
  export function parseStandardRoutes(content) {
26
25
  const routes = [];
27
26
  for (const line of content.split(/\r?\n/)) {
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, readdirSync, existsSync } from 'fs';
2
2
  import { join } from 'path';
3
3
 
4
- /** One-line `key: value` frontmatter. CRLF-safe: a checkout with autocrlf is still read. */
4
+ /** CRLF-safe: a checkout with autocrlf is still read. */
5
5
  export function parseFrontmatter(content) {
6
6
  const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
7
7
  if (!match) return {};
@@ -21,19 +21,9 @@ export function parseFrontmatter(content) {
21
21
  const FENCE = /^(\s*)```/;
22
22
  const DISPATCH_HINT = /dispatch|despach|subagent_type|Template do prompt/i;
23
23
  const PERSONA_REF = /morph-(\{id\}|\{task\.agent\}|[a-z][a-z-]*[a-z])/g;
24
- /** Words after `morph-` that are skills or CLI verbs, never personas. */
25
- /** Claude Code's own sub-agents: dispatched by skills, but not personas of agents.json. */
26
24
  const BUILTIN_AGENT = /`(Explore|general-purpose|Plan)`/g;
27
25
  const NOT_PERSONA = /^(spec|proposal|brainstorming|stack-scan|uiux|plan|implement|apply|review|eval|hotfix|standards|media|task)$/;
28
26
 
29
- /**
30
- * The prompts a skill tells the orchestrator to send to a sub-agent: fenced
31
- * blocks that are either written as a dispatch (`dispatch Agent (…)`) or sit
32
- * right under a line about dispatching. Each carries the personas it names —
33
- * `generic` when the template is `morph-{id}` (any implementer).
34
- *
35
- * @returns {Array<{ index: number, personas: string[], generic: boolean, builtin: string[], context: string, text: string }>}
36
- */
37
27
  export function extractDispatchPrompts(content) {
38
28
  const lines = content.split(/\r?\n/);
39
29
  const out = [];
@@ -45,7 +35,7 @@ export function extractDispatchPrompts(content) {
45
35
  const body = lines.slice(i + 1, j);
46
36
  const text = body.map((l) => l.slice(Math.min(open[1].length, l.length - l.trimStart().length))).join('\n');
47
37
  // Only the sentence that introduces the block counts: a paragraph further up
48
- // that mentions a dispatch (morph-apply's DAG example) is not this block's.
38
+ // that mentions a dispatch is not this block's.
49
39
  let k = i - 1;
50
40
  while (k >= 0 && lines[k].trim() === '') k--;
51
41
  const before = k >= 0 && i - k <= 2 ? lines[k] : '';
@@ -10,12 +10,8 @@ export function scanStandards(frameworkDir, agents) {
10
10
  const refMap = {};
11
11
  for (const agent of agents) {
12
12
  for (const std of personaStandardIds(agent)) {
13
- // agents.json stores standards as plain id STRINGS. Reading `std.id` off
14
- // a string yields undefined, so every reference piled into refMap
15
- // [undefined] and the lookup below missed all 60 — the Standards graph
16
- // drew standards with no agent edge at all. The object form is the v4
17
- // shape (`{ id, scope, priority }`); still accepted so a mixed or
18
- // restored agents.json does not silently lose edges again.
13
+ // agents.json stores standards as id strings; the `{ id }` object form is
14
+ // still accepted so a mixed agents.json does not lose edges.
19
15
  const id = typeof std === 'string' ? std : std?.id;
20
16
  if (!id) continue;
21
17
  if (!refMap[id]) refMap[id] = [];
@@ -21,14 +21,6 @@ function readBody(req) {
21
21
  });
22
22
  }
23
23
 
24
- /**
25
- * Two data paths:
26
- * - `virtual:morph-data` — the framework's knowledge (agents, phases, skills,
27
- * standards, rules, hooks, docs). Static for the life of the server.
28
- * - `/api/morph/*` — the portfolio (`/projects`) and each project's features
29
- * and activity (`/p/:id/…`), read on demand and pushed as `morph:changed`
30
- * over the HMR socket when a project's `.morph/` changes.
31
- */
32
24
  export default function morphDataPlugin(frameworkDir, projectDir) {
33
25
  return {
34
26
  name: 'vite-plugin-morph-data',
@@ -43,11 +35,8 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
43
35
  const portfolio = createPortfolio({ launchDir: projectDir });
44
36
  const push = (data) => server.ws.send({ type: 'custom', event: 'morph:changed', data });
45
37
 
46
- // Vite reloads the whole page for any changed .html it watches, even when
47
- // handleHotUpdate returns [] (vite 6: no module matched + .html = full
48
- // reload). A project's .morph/ carries .html design sources, and an agent
49
- // writing one reloaded the dashboard under the person reading it. The
50
- // dashboard's own code never lives under .morph/.
38
+ // Vite 6 full-reloads any changed .html it watches even when handleHotUpdate
39
+ // returns [], and a project's .morph/ carries .html design sources.
51
40
  const clientHot = server.environments?.client?.hot;
52
41
  if (clientHot?.send) {
53
42
  const send = clientHot.send.bind(clientHot);
@@ -64,10 +53,8 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
64
53
  if (fresh.length) server.watcher.add(fresh);
65
54
  }
66
55
 
67
- // Read the projects in the background, one at a time, so neither the first
68
- // page nor the portfolio waits for a large project. Reading one blocks the
69
- // event loop for seconds, so each step waits WARM_GAP_MS first: the reads
70
- // a push (or an add) just caused are served before the next block.
56
+ // One project at a time, WARM_GAP_MS apart: a read blocks the event loop for
57
+ // seconds, and the reads a push just caused are served first.
71
58
  let warming = false;
72
59
  function warmSoon() {
73
60
  if (warming) return;
@@ -88,7 +75,6 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
88
75
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
89
76
  res.setHeader('Cache-Control', 'no-store');
90
77
  res.end(JSON.stringify(out));
91
- // The portfolio's list changed: watch and read any project just added.
92
78
  if (req.method !== 'GET' && status === 200) { watchNew(); push({ scope: 'projects' }); warmSoon(); }
93
79
  });
94
80
 
@@ -98,7 +84,7 @@ export default function morphDataPlugin(frameworkDir, projectDir) {
98
84
  rescan.unref?.();
99
85
 
100
86
  const timers = new Map();
101
- const changed = new Map(); // `${project}:${scope}` -> path -> watcher event, in the debounce window
87
+ const changed = new Map();
102
88
  const onFsEvent = (event, path) => {
103
89
  const scope = classifyChange(path);
104
90
  if (!scope) return;
@@ -1,24 +1,24 @@
1
- import { defineConfig } from 'vite';
2
- import react from '@vitejs/plugin-react';
3
- import tailwindcss from '@tailwindcss/vite';
4
- import morphDataPlugin from './vite-plugin-morph-data.js';
5
- import { fileURLToPath } from 'url';
6
- import { dirname, resolve } from 'path';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
-
11
- const frameworkDir = resolve(__dirname, '..', '..', 'framework');
12
- const projectDir = process.env.MORPH_PROJECT_DIR || process.cwd();
13
-
14
- export default defineConfig({
15
- root: __dirname,
16
- plugins: [
17
- react(),
18
- tailwindcss(),
19
- morphDataPlugin(frameworkDir, projectDir),
20
- ],
21
- server: {
22
- port: 3200,
23
- },
24
- });
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import morphDataPlugin from './vite-plugin-morph-data.js';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, resolve } from 'path';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ const frameworkDir = resolve(__dirname, '..', '..', 'framework');
12
+ const projectDir = process.env.MORPH_PROJECT_DIR || process.cwd();
13
+
14
+ export default defineConfig({
15
+ root: __dirname,
16
+ plugins: [
17
+ react(),
18
+ tailwindcss(),
19
+ morphDataPlugin(frameworkDir, projectDir),
20
+ ],
21
+ server: {
22
+ port: 3200,
23
+ },
24
+ });
@@ -1,26 +1,5 @@
1
- /**
2
- * ai-checks.js — o coletor das cinco checagens de `morph-spec doctor --ai`.
3
- *
4
- * Contrato de uma checagem:
5
- *
6
- * { id, name, status: 'ok'|'warn'|'error'|'skip', reason?, findings: [], unverified?: [] }
7
- *
8
- * Duas regras que este módulo existe para tornar mecânicas:
9
- *
10
- * 1. **`status: 'skip'` sai com `reason` obrigatória.** "Não verificado" sem
11
- * motivo é ruído — quem lê não consegue decidir se falta insumo ou se falta
12
- * checagem. Um `skip` sem razão é bug deste arquivo, e há canário.
13
- * 2. **`null` de uma função de checagem é DESCONHECIDO, nunca "não achei
14
- * nada".** `[]` é "perguntei e não achei" e vira `ok`. É a doutrina de
15
- * `pr-index.js`, já viva no `doctor`.
16
- *
17
- * E uma proibição: **nada alcançável daqui escreve.** Os candidatos óbvios ao
18
- * erro — `scripts/generate-ai-pin-props.js` e
19
- * `scripts/generate-standards-registry.js` — escrevem por padrão e NÃO entram
20
- * no grafo de imports deste módulo. É a lição do T10 da #90, onde a auditoria
21
- * ia ALOCAR um bloco de porta ao auditar, inventando o estado que reportava.
22
- * O canário assere a lista de chamadas feitas, não só o retorno.
23
- */
1
+ // `skip` sai com `reason` obrigatória, e `null` de uma checagem é DESCONHECIDO, nunca "não achei".
2
+ // Nada alcançável daqui escreve: os geradores de `scripts/` ficam fora do grafo de imports.
24
3
 
25
4
  import { scanAiPackageRefs } from './ai-project-scan.js';
26
5
  import { resolveAiPin, auditPinDrift } from './pin-drift.js';
@@ -30,7 +9,6 @@ import { auditRenamedApi, auditPromptSource, renamedApiGate } from './code-audit
30
9
  /** Os cinco ids, na ordem em que o relatório os imprime. */
31
10
  export const AI_CHECK_IDS = ['pin-drift', 'removed-api', 'pricing-missing', 'api-per-alias', 'prompt-source'];
32
11
 
33
- /** Nome legível de cada checagem. */
34
12
  export const AI_CHECK_NAMES = {
35
13
  'pin-drift': 'pin × projeto (versões de pacote de IA)',
36
14
  'removed-api': 'API renomeada antes do 1.0 GA',
@@ -42,7 +20,6 @@ export const AI_CHECK_NAMES = {
42
20
  /** O pacote cuja versão gateia a checagem de API renomeada. */
43
21
  export const MAF_PACKAGE = 'Microsoft.Agents.AI';
44
22
 
45
- /** As dependências reais. Substituíveis inteiras ou em parte, por canário. */
46
23
  export function defaultAiDeps() {
47
24
  return {
48
25
  scanAiPackageRefs,
@@ -75,19 +52,8 @@ function skipAll(reason) {
75
52
  return AI_CHECK_IDS.map((id) => skip(id, reason));
76
53
  }
77
54
 
78
- /**
79
- * Roda as cinco checagens sobre um projeto.
80
- *
81
- * As três condições de silêncio total acontecem ANTES de qualquer leitura do
82
- * pin — e é essa ordem, não o retorno, que o canário assere: sem `.csproj`
83
- * alcançável, `resolveAiPin` não é sequer chamada.
84
- *
85
- * @param {string} targetPath - raiz do projeto
86
- * @param {object} [options]
87
- * @param {object|null} [options.config] - `.morph/config/config.json` já lido
88
- * @param {object} [options.deps] - substituição parcial de `defaultAiDeps()`
89
- * @returns {Array<{id: string, name: string, status: string, reason?: string, findings: Array}>}
90
- */
55
+ // As condições de silêncio total acontecem ANTES de ler o pin: sem `.csproj` alcançável,
56
+ // `resolveAiPin` nem é chamada.
91
57
  export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
92
58
  const d = { ...defaultAiDeps(), ...deps };
93
59
 
@@ -114,8 +80,7 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
114
80
  const unverified = scan.unreadable.slice();
115
81
  const checks = [];
116
82
 
117
- // ── pin-drift ────────────────────────────────────────────────────────────
118
- // Condição 2: sem pin resolvível, a checagem que depende dele sai skip.
83
+ // Sem pin resolvível, a checagem que depende dele sai skip.
119
84
  const pin = d.resolveAiPin(targetPath);
120
85
  if (!pin) {
121
86
  checks.push(skip('pin-drift', 'nenhum `ai-pin.json` resolvível (nem em `.morph/framework/`, nem '
@@ -127,7 +92,6 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
127
92
  : resolved('pin-drift', findings, { unverified }));
128
93
  }
129
94
 
130
- // ── removed-api ──────────────────────────────────────────────────────────
131
95
  const maf = scan.packages.find((p) => p.name === MAF_PACKAGE) ?? null;
132
96
  const gate = d.renamedApiGate(maf);
133
97
  if (gate !== null) {
@@ -139,7 +103,6 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
139
103
  : resolved('removed-api', findings));
140
104
  }
141
105
 
142
- // ── pricing-missing e api-per-alias ──────────────────────────────────────
143
106
  const registry = d.locateAiRegistry(targetPath, config);
144
107
  const noRegistry = 'nenhum registry de modelos encontrado — nem `project.ai.modelRegistry` no '
145
108
  + 'config, nem um `.json` com `defaultAlias` + mapa de aliases, nem uma seção `Pricing` num '
@@ -155,7 +118,6 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
155
118
  : noRegistry)
156
119
  : resolved('api-per-alias', perAlias));
157
120
 
158
- // ── prompt-source ────────────────────────────────────────────────────────
159
121
  const prompts = d.auditPromptSource(targetPath);
160
122
  checks.push(prompts === null
161
123
  ? skip('prompt-source', 'nenhum `.cs` alcançável a partir desta raiz')
@@ -164,19 +126,12 @@ export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
164
126
  return checks;
165
127
  }
166
128
 
167
- /**
168
- * Código de saída do `doctor --ai`: 1 quando há ao menos um `error`, 0 caso
169
- * contrário. `warn` e `skip` NUNCA mudam o código de saída — só `pin-drift`
170
- * produz `error`, e só na faixa flutuante, a única classe com prejuízo medido.
171
- *
172
- * @param {Array<{status: string}>} checks
173
- * @returns {0|1}
174
- */
129
+ /** `warn` e `skip` nunca mudam o código de saída: só `pin-drift` produz `error`, na faixa flutuante. */
175
130
  export function aiExitCode(checks) {
176
131
  return checks.some((c) => c.status === 'error') ? 1 : 0;
177
132
  }
178
133
 
179
- /** true quando NENHUMA checagem teve insumo — o relatório sai em uma linha. */
134
+ /** true quando nenhuma checagem teve insumo: o relatório sai em uma linha. */
180
135
  export function allSkipped(checks) {
181
136
  return checks.length > 0 && checks.every((c) => c.status === 'skip');
182
137
  }