@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
@@ -3,25 +3,21 @@ import { existsSync, readFileSync } from 'fs';
3
3
  import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
4
4
  import { frozenBody } from '../../../framework/hooks/shared/errata.js';
5
5
  import { readAgentsJson } from '../specs/screen-checklist.js';
6
+ import { getFeature } from '../../../framework/hooks/shared/state-reader.js';
6
7
  import { findArtifactTerms } from './artifact-vocabulary.js';
7
- import { PROOF_KINDS, REUSE_VERDICTS, ADR_SCOPES, SECTIONS, CONFLICTS_HEADING } from './plan-parser.js';
8
+ import { PROOF_KINDS, RISK_KINDS, REUSE_VERDICTS, ADR_SCOPES, SECTIONS, CONFLICTS_HEADING } from './plan-parser.js';
8
9
 
9
10
  export const CHECKS_VERSION = 1;
10
11
  export const MAX_SLICE_FILES = 8;
11
12
  export const MAX_SLICES = 12;
12
13
  export const MAX_PARALLEL = 4;
13
14
 
14
- /** sha256 of the plan without its errata: appending a correction never invalidates checks.json. */
15
+ /** sha256 of the plan without its errata: appending a correction never invalidates the extracted checks. */
15
16
  export function planHash(text) {
16
17
  return createHash('sha256').update(frozenBody(text), 'utf8').digest('hex');
17
18
  }
18
19
 
19
- /**
20
- * Persona ids the project can dispatch: the installed agents.json, else the package's.
21
- *
22
- * @param {string} cwd
23
- * @returns {string[]|null} null when no agents.json is readable — personas then go unchecked
24
- */
20
+ /** The installed agents.json, else the package's; null when unreadable (personas then go unchecked). */
25
21
  export function knownPersonas(cwd) {
26
22
  const agents = readAgentsJson(cwd)?.agents;
27
23
  return agents && typeof agents === 'object' ? Object.keys(agents) : null;
@@ -54,14 +50,8 @@ export function dependencyCycle(slices) {
54
50
 
55
51
  const fileKey = (f) => f.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
56
52
 
57
- /**
58
- * Waves of slices that may run at the same time: a slice joins a wave only
59
- * after every slice it depends on ran in an earlier one, and never with a
60
- * slice that touches one of its files. At most MAX_PARALLEL per wave.
61
- *
62
- * @param {{ id: string, depends?: string[], files?: string[] }[]} slices
63
- * @returns {string[][]} empty when the dependencies have a cycle
64
- */
53
+ // A slice joins a wave only after its dependencies ran, never with one touching its files, and at most
54
+ // MAX_PARALLEL per wave. Empty on a dependency cycle.
65
55
  export function parallelGroups(slices) {
66
56
  if (dependencyCycle(slices)) return [];
67
57
  const ids = new Set(slices.map((s) => s.id));
@@ -94,14 +84,19 @@ export function parallelGroups(slices) {
94
84
  return groups;
95
85
  }
96
86
 
97
- /**
98
- * Judge a parsed plan.
99
- *
100
- * @param {ReturnType<import('./plan-parser.js').parsePlan>} plan
101
- * @param {{ personas?: string[]|null, surfaces?: string[]|null }} [known]
102
- * @returns {{ errors: string[], warnings: string[] }}
103
- */
104
- export function validatePlan(plan, { personas = null, surfaces = null } = {}) {
87
+ // A frozen body already extracted under older rules cannot gain a `risco:` line, so it is not demanded.
88
+ export function planRuleOptions(cwd, featureName) {
89
+ return {
90
+ workType: getFeature(featureName, cwd)?.workType ?? null,
91
+ riskRules: readPlanChecks(cwd, featureName).status !== 'fresh',
92
+ };
93
+ }
94
+
95
+ const foldRisk = (s) => s.normalize('NFD').replace(/\p{M}/gu, '').replace(/`/g, '').toLowerCase().trim();
96
+ const RISK_BY_FOLD = new Map(RISK_KINDS.map((r) => [foldRisk(r), r]));
97
+ const REGRESSION_WORK_TYPES = ['bug', 'hotfix'];
98
+
99
+ export function validatePlan(plan, { personas = null, surfaces = null, workType = null, riskRules = true } = {}) {
105
100
  const errors = [];
106
101
  const warnings = [];
107
102
  const at = (line) => (line ? ` (linha ${line})` : '');
@@ -132,12 +127,26 @@ export function validatePlan(plan, { personas = null, surfaces = null } = {}) {
132
127
  } else if (!PROOF_KINDS.includes(c.proof.toLowerCase())) {
133
128
  errors.push(`${c.id}: prova "${c.proof}" fora de ${PROOF_KINDS.join(' | ')}${at(c.line)}`);
134
129
  }
130
+ const proof = c.proof?.toLowerCase();
131
+ const risk = c.risk === null ? null : RISK_BY_FOLD.get(foldRisk(c.risk)) ?? false;
132
+ if (risk === false) {
133
+ errors.push(`${c.id}: risco "${c.risk}" fora de ${RISK_KINDS.join(' | ')}${at(c.line)}`);
134
+ } else if (riskRules && proof === 'teste' && risk === null) {
135
+ errors.push(`${c.id}: "prova: teste" sem "risco:" (${RISK_KINDS.join(' | ')}) — sem risco, a prova é "leitura" ou "e2e"${at(c.line)}`);
136
+ } else if (risk && proof === 'leitura') {
137
+ warnings.push(`${c.id}: risco "${risk}" provado só por leitura${at(c.line)}`);
138
+ }
135
139
  const terms = findArtifactTerms(c.text);
136
140
  if (terms.length > 0 && !c.artifactWhy) {
137
141
  errors.push(`${c.id} descreve artefato ("${terms.join(', ')}") sem "> Por que artefato:" — reescreva como efeito observável ou justifique${at(c.line)}`);
138
142
  }
139
143
  }
140
144
 
145
+ const isRegression = (c) => ['teste', 'e2e'].includes(c.proof?.toLowerCase()) && c.risk !== null && RISK_BY_FOLD.get(foldRisk(c.risk)) === 'regressão';
146
+ if (riskRules && REGRESSION_WORK_TYPES.includes(workType) && plan.scenarios.length > 0 && !plan.scenarios.some(isRegression)) {
147
+ errors.push(`${workType} sem cenário de regressão — o sintoma do bug real vira um Cn com "prova: teste" e "risco: regressão"`);
148
+ }
149
+
141
150
  const scenarioIds = new Set(plan.scenarios.map((c) => c.id));
142
151
  const sliceIds = new Set(plan.slices.map((s) => s.id));
143
152
  const covered = new Set();
@@ -184,7 +193,6 @@ export function validatePlan(plan, { personas = null, surfaces = null } = {}) {
184
193
  return { errors, warnings };
185
194
  }
186
195
 
187
- /** The checks.json document for a parsed plan. */
188
196
  export function buildChecks(feature, text, plan) {
189
197
  return {
190
198
  version: CHECKS_VERSION,
@@ -202,14 +210,7 @@ export function buildChecks(feature, text, plan) {
202
210
  };
203
211
  }
204
212
 
205
- /**
206
- * The feature's extracted checks and whether they still describe the plan on
207
- * disk. plan-check writes them only with zero errors, so `fresh` also means
208
- * the plan passed every plan-check rule (no slice over MAX_SLICE_FILES, every
209
- * scenario covered).
210
- *
211
- * @returns {{ status: 'fresh'|'no-plan'|'no-checks'|'unreadable'|'stale', checks: object|null }}
212
- */
213
+ /** plan-check writes the checks only with zero errors, so `fresh` also means the plan passed every rule. */
213
214
  export function readPlanChecks(cwd, featureName) {
214
215
  const planPath = getAbsoluteOutputPath(cwd, featureName, 'plan');
215
216
  const checksPath = getAbsoluteOutputPath(cwd, featureName, 'checks');
@@ -2,7 +2,8 @@ import { tableCells, isTableLine, SEPARATOR_ROW_RE } from '../specs/gfm-table.js
2
2
  import { ARTIFACT_MARKER_RE } from './artifact-vocabulary.js';
3
3
  import { markdownLines } from '../../../framework/hooks/shared/errata.js';
4
4
 
5
- export const PROOF_KINDS = ['teste', 'e2e', 'eval', 'manual'];
5
+ export const PROOF_KINDS = ['teste', 'e2e', 'eval', 'manual', 'leitura'];
6
+ export const RISK_KINDS = ['regra', 'dinheiro', 'contrato', 'concorrência', 'dado', 'regressão'];
6
7
  export const REUSE_VERDICTS = ['REUSAR', 'ESTENDER', 'CRIAR'];
7
8
  export const ADR_SCOPES = ['feature', 'projeto'];
8
9
 
@@ -36,6 +37,7 @@ const SCENARIO_RE = new RegExp(String.raw`^[-*]\s+\*\*(C\d+)\*\*${DASH}(.+)$`);
36
37
  const ADR_RE = new RegExp(String.raw`^###\s+(ADR-\d+)${DASH}(.+?)\s*$`);
37
38
  const SLICE_RE = new RegExp(String.raw`^###\s+(F\d+)${DASH}(.+?)\s*$`);
38
39
  const PROOF_RE = /^\s*prova\s*:\s*(.*?)\s*$/i;
40
+ const RISK_RE = /^\s*risco\s*:\s*(.*?)\s*$/i;
39
41
  const WHY_RE = new RegExp(String.raw`^\s*>\s*` + ARTIFACT_MARKER_RE.source + String.raw`\s*(.*?)\s*$`, 'i');
40
42
  const SCOPE_RE = /^\s*\*\*Alcance:\*\*\s*(.*?)\s*$/i;
41
43
  const SLICE_LINE_RE = /^\s*[-*]\s+([\p{L}]+)\s*:\s*(.*?)\s*$/u;
@@ -69,7 +71,7 @@ function parseScenarios(lines) {
69
71
  if (line.fenced) { current = null; continue; }
70
72
  const m = SCENARIO_RE.exec(line.raw);
71
73
  if (m) {
72
- current = { id: m[1], text: m[2].trim(), proof: null, artifactWhy: null, line: line.n };
74
+ current = { id: m[1], text: m[2].trim(), proof: null, risk: null, artifactWhy: null, line: line.n };
73
75
  scenarios.push(current);
74
76
  continue;
75
77
  }
@@ -79,6 +81,8 @@ function parseScenarios(lines) {
79
81
  if (why) { current.artifactWhy = why[1] || ''; continue; }
80
82
  const proof = PROOF_RE.exec(line.raw);
81
83
  if (proof && current.proof === null) { current.proof = proof[1]; continue; }
84
+ const risk = RISK_RE.exec(line.raw);
85
+ if (risk && current.risk === null) { current.risk = risk[1]; continue; }
82
86
  if (/^\s*>/.test(line.raw)) continue;
83
87
  current.text = `${current.text} ${line.raw.trim()}`;
84
88
  }
@@ -175,12 +179,7 @@ function parseSlices(lines) {
175
179
  return slices;
176
180
  }
177
181
 
178
- /**
179
- * Parse `2-plan/plan.md` into its structure. Pure: no validation, no I/O —
180
- * `checks.js` judges what this returns.
181
- *
182
- * @param {string} text
183
- */
182
+ /** Pure: no validation, no I/O; `checks.js` judges what this returns. */
184
183
  export function parsePlan(text) {
185
184
  const lines = markdownLines(text);
186
185
  const sections = splitSections(lines);
@@ -1,19 +1,5 @@
1
- /**
2
- * Where the morph-spec projects on this machine are — the list the dashboard's
3
- * portfolio reads. Nothing else records it: each project only knows its own
4
- * `.morph/`.
5
- *
6
- * Three writers register a project as a side note of what they already do —
7
- * `init`, `update` (never under `--dry-run`) and `dashboard` — and the
8
- * dashboard adds and removes on request. Registering is fail-open: a registry
9
- * that cannot be read or written never fails the command that tried.
10
- *
11
- * The file lives in the user's home (`~/.morph-spec/projects.json`, or
12
- * `MORPH_PROJECTS_REGISTRY`), read through `os.homedir()` at call time so a
13
- * suite that isolates the home also isolates this file. Paths under the
14
- * system temp dir are never registered: the test suites run `init`/`update`
15
- * there by the hundred, including through spawned CLIs that keep the real home.
16
- */
1
+ // Paths under the system temp dir are never registered: test suites run `init`/`update` there by the
2
+ // hundred, through spawned CLIs that keep the real home. The home is read at call time so isolating it works.
17
3
 
18
4
  import { existsSync, readFileSync, readdirSync } from 'fs';
19
5
  import { homedir, tmpdir } from 'os';
@@ -35,7 +21,6 @@ export function projectKey(dir) {
35
21
 
36
22
  const sameDir = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
37
23
 
38
- /** A folder is a morph-spec project when morph-spec installed its config there. */
39
24
  export function isMorphProject(dir) {
40
25
  return existsSync(join(dir, '.morph', 'config', 'config.json'));
41
26
  }
@@ -61,18 +46,13 @@ function projectName(dir) {
61
46
  }
62
47
  }
63
48
 
64
- /**
65
- * The registered projects that still exist. A folder that was deleted or lost
66
- * its `.morph/` drops out on read — the file is rewritten on the next write.
67
- *
68
- * @returns {{ version: number, projects: Array<{ dir: string, name: string, addedAt: string, lastSeenAt: string, source: string }> }}
69
- */
49
+ /** Folders that were deleted or lost `.morph/` drop out on read. */
70
50
  export function readRegistry() {
71
51
  let data = null;
72
52
  try {
73
53
  const path = registryPath();
74
54
  if (existsSync(path)) data = JSON.parse(readFileSync(path, 'utf8'));
75
- } catch { /* unreadable registry reads as empty */ }
55
+ } catch {}
76
56
  const projects = Array.isArray(data?.projects) ? data.projects : [];
77
57
  return {
78
58
  version: REGISTRY_VERSION,
@@ -84,16 +64,7 @@ function write(projects) {
84
64
  atomicWriteJson(registryPath(), { version: REGISTRY_VERSION, projects });
85
65
  }
86
66
 
87
- /**
88
- * Note that `dir` is a morph-spec project. Idempotent; refreshes `lastSeenAt`.
89
- * Never throws.
90
- *
91
- * @param {string} dir
92
- * @param {'init'|'update'|'dashboard'|'manual'} source
93
- * @param {{ tempRoot?: string }} [opts] - test seam: the folder whose
94
- * projects are never registered (the system temp dir)
95
- * @returns {boolean} true when the project is in the registry afterwards
96
- */
67
+ /** Idempotent, never throws; `tempRoot` is the test seam for the never-registered folder. */
97
68
  export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
98
69
  try {
99
70
  const key = projectKey(dir);
@@ -115,7 +86,7 @@ export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
115
86
  }
116
87
  }
117
88
 
118
- /** Take a project off the list. Its files are not touched. Never throws. */
89
+ /** Never throws; the project's files are not touched. */
119
90
  export function unregisterProject(dir) {
120
91
  try {
121
92
  const key = projectKey(dir);
@@ -128,15 +99,7 @@ export function unregisterProject(dir) {
128
99
  }
129
100
  }
130
101
 
131
- /**
132
- * morph-spec projects directly under each root that are not registered yet —
133
- * the portfolio offers them, the person decides. One level deep: a repos
134
- * folder is the expected root, and a deeper walk would enter node_modules and
135
- * worktrees for nothing.
136
- *
137
- * @param {string[]} roots
138
- * @returns {Array<{ dir: string, name: string }>}
139
- */
102
+ /** Unregistered projects one level under each root: deeper would walk node_modules and worktrees. */
140
103
  export function discoverProjects(roots) {
141
104
  const registered = readRegistry().projects.map((p) => p.dir);
142
105
  const out = [];
@@ -27,10 +27,11 @@ const CHECKS_SECTION_RE = sectionRe('Checks');
27
27
  const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
28
28
  const SCREEN_CHECKLIST_SECTION_RE = sectionRe('Checklist\\s+de\\s+tela');
29
29
 
30
- /** Hotfix: só o verificador, escrito pelo orquestrador sem dispatch. */
30
+ /** Hotfix: só o verificador, escrito pelo orquestrador sem dispatch. Chore: só o revisor (review leve). */
31
31
  export function requiredReviewRoles({ workType, securityRequired = false } = {}) {
32
32
  if (workType === 'hotfix') return ['verifier'];
33
- return securityRequired ? ['verifier', 'reviewer', 'security'] : ['verifier', 'reviewer'];
33
+ const base = workType === 'chore' ? ['reviewer'] : ['verifier', 'reviewer'];
34
+ return securityRequired ? [...base, 'security'] : base;
34
35
  }
35
36
 
36
37
  function sectionBodies(text, re) {
@@ -60,7 +61,7 @@ function tableRows(body) {
60
61
  return rows;
61
62
  }
62
63
 
63
- /** `| id | tipo | onde | evidência |`; `untyped` = tipo fora de {@link BLOCKER_TYPES}. */
64
+ // `| id | tipo | onde | evidência |`; `untyped` = tipo fora de BLOCKER_TYPES.
64
65
  function parseBlockers(body) {
65
66
  const blockers = [];
66
67
  const untyped = [];
@@ -74,10 +75,7 @@ function parseBlockers(body) {
74
75
 
75
76
  const STATUS_BY_KEY = Object.fromEntries(CHECK_STATUSES.map((s) => [normalize(s), s]));
76
77
 
77
- /**
78
- * `| Cn | PROVADO|NÃO PROVADO|FALHA | evidência |` de TODAS as seções: uma confirmação
79
- * traz só os Cn que mudou, então vale a última linha de cada Cn. Status por igualdade exata.
80
- */
78
+ // Vale a última linha de cada Cn em todas as seções: uma confirmação traz só os Cn que mudou.
81
79
  function parseChecks(bodies) {
82
80
  const byId = new Map();
83
81
  for (const cells of bodies.flatMap(tableRows)) {
@@ -110,10 +108,7 @@ export function parseReviewReport(text) {
110
108
  };
111
109
  }
112
110
 
113
- /**
114
- * `missing`: sem arquivo. `partial`: `PARCIAL` na 1ª linha, com ou sem veredito.
115
- * `unverdicted`: sem `**Verdict:** SHIP|FIX`. `ok`: legível — quem decide é o risk-detector.
116
- */
111
+ // `partial`: `PARCIAL` na 1ª linha, com ou sem veredito. `ok` só diz legível: quem decide é o risk-detector.
117
112
  function stateOf(path, text) {
118
113
  if (text === null) return { status: 'missing', path };
119
114
  const parsed = parseReviewReport(text);
@@ -148,13 +143,7 @@ export function reviewReports(cwd, featureName) {
148
143
  return reviewReportsFromDir(join(cwd, '.morph', 'features', featureName));
149
144
  }
150
145
 
151
- /**
152
- * Todo Cn precisa de uma linha, e só `PROVADO` cobre.
153
- *
154
- * @param {string[]} checkIds
155
- * @param {{id: string, status: string}[]|null} rows - `parseReviewReport(...).checks`
156
- * @returns {{ missing: string[], notProven: {id: string, status: string}[] }}
157
- */
146
+ /** Todo Cn precisa de uma linha, e só `PROVADO` cobre. */
158
147
  export function checksCoverage(checkIds, rows) {
159
148
  const byId = new Map((rows ?? []).map((r) => [r.id, r]));
160
149
  const missing = [];
@@ -167,12 +156,7 @@ export function checksCoverage(checkIds, rows) {
167
156
  return { missing, notProven };
168
157
  }
169
158
 
170
- /**
171
- * O sinal que `reviewPause` (risk-detector) lê; `checkIds: null` = cobertura não conferível.
172
- *
173
- * @param {ReturnType<typeof reviewReportsFromDir>} reports
174
- * @param {{ required: string[], checkIds: string[]|null }} opts
175
- */
159
+ /** O sinal que `reviewPause` lê; `checkIds: null` = cobertura não conferível. */
176
160
  export function reviewSignal(reports, { required, checkIds }) {
177
161
  const out = { required, reports: {} };
178
162
  for (const role of ROLE_ORDER) {
@@ -189,7 +173,9 @@ export function reviewSignal(reports, { required, checkIds }) {
189
173
  superseded: r.superseded,
190
174
  };
191
175
  }
192
- out.checks = checkIds === null
176
+ out.checks = !required.includes('verifier')
177
+ ? { status: 'not-required', missing: [], notProven: [] }
178
+ : checkIds === null
193
179
  ? { status: 'no-checks', missing: [], notProven: [] }
194
180
  : { status: 'ok', total: checkIds.length, ...checksCoverage(checkIds, reports.verifier.checks) };
195
181
  return out;
@@ -205,7 +191,6 @@ export function combinedVerdict(reports, required) {
205
191
  return required.every((r) => reports[r].status === 'ok' && reports[r].verdict === 'SHIP') ? 'SHIP' : null;
206
192
  }
207
193
 
208
- /** Todos os bloqueadores tipados dos relatórios presentes, com o papel que os abriu. */
209
194
  export function allBlockers(reports) {
210
195
  return ROLE_ORDER.flatMap((role) => (reports[role].blockers ?? []).map((b) => ({ ...b, role })));
211
196
  }