@polymorphism-tech/morph-spec 8.46.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CLAUDE.md +53 -44
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +38 -55
  4. package/bin/validate.js +5 -134
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -2
  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 +75 -18
  11. package/framework/commands/create-umbraco-lp.md +2 -0
  12. package/framework/commands/migrate-lp-to-umbraco.md +2 -0
  13. package/framework/commands/morph-apply.md +5 -28
  14. package/framework/commands/morph-archive.md +2 -2
  15. package/framework/commands/morph-hotfix.md +8 -8
  16. package/framework/commands/morph-infra.md +17 -203
  17. package/framework/commands/morph-media.md +3 -0
  18. package/framework/commands/morph-preflight.md +7 -31
  19. package/framework/commands/morph-proposal.md +2 -50
  20. package/framework/commands/morph-status.md +3 -3
  21. package/framework/commands/morph-troubleshoot.md +2 -14
  22. package/framework/docs/CLI.md +585 -286
  23. package/framework/evals/review-checklist.md +82 -41
  24. package/framework/hooks/README.md +10 -11
  25. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  26. package/framework/hooks/claude-code/core/denylist.json +33 -3
  27. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  28. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  30. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  31. package/framework/hooks/claude-code/core/state-sync.js +17 -19
  32. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  33. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  34. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  35. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  36. package/framework/hooks/claude-code/statusline.py +55 -41
  37. package/framework/hooks/package.json +3 -0
  38. package/framework/hooks/shared/activity-logger.js +1 -1
  39. package/framework/hooks/shared/build-check.js +10 -6
  40. package/framework/hooks/shared/command-tokens.js +169 -0
  41. package/framework/hooks/shared/comment-rules.js +1 -1
  42. package/framework/hooks/shared/comment-scanner.js +10 -23
  43. package/framework/hooks/shared/comment-stripper.js +1 -1
  44. package/framework/hooks/shared/e2e-check.js +67 -31
  45. package/framework/hooks/shared/errata.js +72 -0
  46. package/framework/hooks/shared/feature-locator.js +1 -1
  47. package/framework/hooks/shared/frontend-locations.js +6 -0
  48. package/framework/hooks/shared/gate-question.js +18 -0
  49. package/framework/hooks/shared/phase-utils.js +15 -32
  50. package/framework/hooks/shared/slice-progress.js +118 -0
  51. package/framework/hooks/shared/spawn-command.js +23 -0
  52. package/framework/hooks/shared/state-reader.js +61 -87
  53. package/framework/hooks/shared/state-writer.js +3 -3
  54. package/framework/mcp/registry.json +3 -4
  55. package/framework/phases.json +23 -39
  56. package/framework/rules/ai-agents-standards.md +18 -16
  57. package/framework/rules/asaas-standards.md +3 -1
  58. package/framework/rules/csharp-standards.md +10 -8
  59. package/framework/rules/data-standards.md +5 -5
  60. package/framework/rules/dotnet-templates.md +1 -1
  61. package/framework/rules/frontend-standards.md +4 -2
  62. package/framework/rules/gsap-standards.md +10 -5
  63. package/framework/rules/hangfire-standards.md +3 -1
  64. package/framework/rules/infrastructure-standards.md +4 -2
  65. package/framework/rules/neon-auth-standards.md +3 -1
  66. package/framework/rules/neon-standards.md +6 -4
  67. package/framework/rules/nextjs-standards.md +13 -11
  68. package/framework/rules/nextjs-templates.md +1 -1
  69. package/framework/rules/resend-standards.md +3 -1
  70. package/framework/schemas/config.schema.json +14 -9
  71. package/framework/schemas/feature.schema.json +15 -52
  72. package/framework/skills/README.md +4 -5
  73. package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
  74. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
  75. package/framework/skills/core/morph-apply/SKILL.md +150 -223
  76. package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
  77. package/framework/skills/core/morph-machine/SKILL.md +4 -4
  78. package/framework/skills/core/morph-media/SKILL.md +3 -3
  79. package/framework/skills/core/morph-plan/SKILL.md +138 -300
  80. package/framework/skills/core/morph-review/SKILL.md +112 -137
  81. package/framework/skills/core/morph-standards/SKILL.md +29 -41
  82. package/framework/skills/core/morph-uiux/SKILL.md +78 -153
  83. package/framework/standards/STANDARDS.json +90 -1219
  84. package/framework/standards/STANDARDS.meta.json +1305 -0
  85. package/framework/standards/ai-agents/evals-with-cache.md +2 -3
  86. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  87. package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
  88. package/framework/standards/ai-agents/prompt-sources.md +6 -4
  89. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  90. package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
  91. package/framework/standards/ai-agents/setup.md +2 -2
  92. package/framework/standards/ai-agents/structured-output.md +1 -1
  93. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  94. package/framework/standards/architecture/code-comments.md +8 -8
  95. package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
  96. package/framework/standards/backend/api/validation.md +135 -47
  97. package/framework/standards/backend/database/postgresql/database.md +11 -7
  98. package/framework/standards/backend/database/vector-search-rag.md +207 -324
  99. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
  100. package/framework/standards/backend/dotnet/result-pattern.md +40 -6
  101. package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
  102. package/framework/standards/backend/dotnet/scrutor.md +1 -1
  103. package/framework/standards/backend/dotnet/testing.md +1 -1
  104. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
  105. package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
  106. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
  107. package/framework/standards/data/nosql/cache/redis.md +120 -44
  108. package/framework/standards/data/nosql/cosmos-db.md +114 -71
  109. package/framework/standards/frontend/design-system/animations.md +1 -1
  110. package/framework/standards/frontend/design-system/naming.md +1 -1
  111. package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
  112. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
  113. package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
  114. package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
  115. package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
  116. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  117. package/framework/templates/README.md +2 -3
  118. package/framework/templates/REGISTRY.json +6 -170
  119. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  120. package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
  121. package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
  122. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
  123. package/framework/templates/docs/plan.md +72 -0
  124. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  127. package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
  128. package/framework/templates/infrastructure/github/README.md +2 -1
  129. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
  130. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  131. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  132. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  133. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  134. package/package.json +1 -1
  135. package/src/commands/e2e/e2e.js +2 -2
  136. package/src/commands/observability/cost.js +18 -60
  137. package/src/commands/observability/telemetry.js +3 -5
  138. package/src/commands/project/doctor-procs.js +1 -1
  139. package/src/commands/project/doctor.js +9 -9
  140. package/src/commands/project/fleet.js +4 -4
  141. package/src/commands/project/heavy.js +3 -3
  142. package/src/commands/project/init.js +6 -1
  143. package/src/commands/project/setup-infra-cmd.js +2 -1
  144. package/src/commands/project/stack-scan.js +58 -0
  145. package/src/commands/project/standards.js +79 -25
  146. package/src/commands/project/status.js +29 -69
  147. package/src/commands/project/update.js +33 -19
  148. package/src/commands/project/worktree.js +1 -1
  149. package/src/commands/state/advance.js +62 -46
  150. package/src/commands/state/approve.js +95 -138
  151. package/src/commands/state/archive.js +20 -84
  152. package/src/commands/state/auto-approval.js +68 -0
  153. package/src/commands/state/create.js +28 -1
  154. package/src/commands/state/delete.js +1 -1
  155. package/src/commands/state/finish.js +11 -23
  156. package/src/commands/state/gate-check.js +86 -95
  157. package/src/commands/state/gate-decision.js +6 -4
  158. package/src/commands/state/handoff.js +1 -1
  159. package/src/commands/state/list.js +9 -9
  160. package/src/commands/state/report-check.js +157 -311
  161. package/src/commands/state/retype.js +9 -3
  162. package/src/commands/validation/mutate.js +77 -14
  163. package/src/commands/validation/plan-check.js +81 -0
  164. package/src/commands/validation/validate-feature.js +60 -135
  165. package/src/commands/validation/verify.js +41 -82
  166. package/src/core/paths/output-schema.js +20 -67
  167. package/src/core/state/gate-order.js +5 -6
  168. package/src/core/state/lifecycle-audit.js +2 -2
  169. package/src/core/state/state-manager.js +17 -98
  170. package/src/core/templates/template-registry.js +2 -2
  171. package/src/core/templates/template-seed.js +22 -0
  172. package/src/core/work-types.js +10 -10
  173. package/src/dashboard/app/App.jsx +0 -2
  174. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  175. package/src/dashboard/app/components/CommandPalette.jsx +7 -8
  176. package/src/dashboard/app/components/EntityLink.jsx +1 -2
  177. package/src/dashboard/app/components/Markdown.jsx +1 -1
  178. package/src/dashboard/app/components/Sidebar.jsx +1 -2
  179. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  180. package/src/dashboard/app/lib/phases.js +2 -4
  181. package/src/dashboard/app/pages/Activity.jsx +1 -1
  182. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  183. package/src/dashboard/app/pages/Features.jsx +9 -9
  184. package/src/dashboard/app/pages/Guide.jsx +2 -2
  185. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  186. package/src/dashboard/app/pages/RulesMap.jsx +3 -3
  187. package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
  188. package/src/dashboard/app/pages/Standards.jsx +1 -1
  189. package/src/dashboard/scanner/activity-scanner.js +6 -16
  190. package/src/dashboard/scanner/agents-scanner.js +1 -1
  191. package/src/dashboard/scanner/docs-scanner.js +5 -23
  192. package/src/dashboard/scanner/features-scanner.js +95 -60
  193. package/src/dashboard/scanner/index.js +0 -2
  194. package/src/dashboard/scanner/project-api.js +1 -1
  195. package/src/dashboard/scanner/rules-scanner.js +15 -10
  196. package/src/dashboard/scanner/skills-scanner.js +2 -3
  197. package/src/dashboard/scanner/standards-scanner.js +2 -1
  198. package/src/lib/ci/ci-verify.js +17 -1
  199. package/src/lib/ci/github-ci.js +1 -1
  200. package/src/lib/detectors/index.js +3 -8
  201. package/src/lib/generators/handoff-generator.js +34 -65
  202. package/src/lib/generators/settings-generator.js +11 -89
  203. package/src/lib/git/evidence-staleness.js +1 -1
  204. package/src/lib/git/tree-fingerprint.js +19 -7
  205. package/src/lib/git/tree-state.js +2 -2
  206. package/src/lib/heavy/cleanup.js +1 -5
  207. package/src/lib/installers/memory-installer.js +6 -8
  208. package/src/lib/mutation/commands.js +3 -2
  209. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  210. package/src/lib/mutation/plan.js +14 -6
  211. package/src/lib/mutation/runner.js +1 -1
  212. package/src/lib/mutation/subset.js +46 -9
  213. package/src/lib/plan/artifact-vocabulary.js +60 -0
  214. package/src/lib/plan/checks.js +234 -0
  215. package/src/lib/plan/plan-parser.js +209 -0
  216. package/src/lib/plan/plan-template.js +18 -0
  217. package/src/lib/review-reports.js +222 -0
  218. package/src/lib/risk-detector.js +134 -135
  219. package/src/lib/specs/adr-index.js +50 -69
  220. package/src/lib/specs/design-seed.js +18 -0
  221. package/src/lib/specs/frontmatter.js +44 -0
  222. package/src/lib/specs/gfm-table.js +17 -0
  223. package/src/lib/specs/old-format.js +68 -0
  224. package/src/lib/specs/revocations.js +37 -95
  225. package/src/lib/specs/screen-checklist.js +18 -17
  226. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  227. package/src/lib/stack/stack-profile.js +1 -1
  228. package/src/lib/stack/stack-scan.js +267 -0
  229. package/src/lib/standards/registry.js +43 -0
  230. package/src/lib/standards/sections.js +58 -0
  231. package/src/lib/tasks/eval-node.js +4 -12
  232. package/src/lib/tasks/test-nodes.js +1 -74
  233. package/src/lib/tasks/test-runner.js +75 -117
  234. package/src/lib/telemetry/metrics.js +8 -69
  235. package/src/lib/telemetry/telemetry-reader.js +5 -118
  236. package/src/lib/telemetry/verify-event.js +5 -7
  237. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  238. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  239. package/src/lib/validators/reuse/reuse-validator.js +24 -48
  240. package/src/lib/validators/reuse/symbol-scanner.js +2 -4
  241. package/src/lib/validators/traceability-check.js +85 -129
  242. package/src/lib/validators/validation-runner.js +8 -88
  243. package/src/lib/validators/verify-runner.js +134 -301
  244. package/src/lib/worktree/docker-audit.js +1 -2
  245. package/src/lib/worktree/fleet-scanner.js +25 -38
  246. package/src/scripts/setup-infra.js +42 -22
  247. package/src/utils/agents-installer.js +92 -13
  248. package/src/utils/claude-settings-manager.js +0 -35
  249. package/src/utils/file-copier.js +34 -6
  250. package/src/utils/hooks-installer.js +16 -9
  251. package/src/utils/skills-installer.js +1 -2
  252. package/framework/commands/commit.md +0 -166
  253. package/framework/evals/trace-schema.md +0 -67
  254. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  255. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  256. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  257. package/framework/hooks/shared/tasks-validator.js +0 -111
  258. package/framework/rubrics/backend.json +0 -41
  259. package/framework/rubrics/default.json +0 -36
  260. package/framework/rubrics/frontend.json +0 -36
  261. package/framework/rules/morph-task-tracking.md +0 -54
  262. package/framework/rules/morph-workflow.md +0 -244
  263. package/framework/schemas/tasks.schema.json +0 -79
  264. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  265. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  266. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  267. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  268. package/framework/templates/docs/proposal.md +0 -192
  269. package/framework/templates/docs/spec.md +0 -57
  270. package/framework/templates/docs/ui-components.md +0 -52
  271. package/framework/templates/docs/ui-design-system.md +0 -76
  272. package/framework/templates/docs/ui-flows.md +0 -167
  273. package/framework/templates/examples/spec-examples.md +0 -91
  274. package/framework/templates/feature/decisions.md +0 -124
  275. package/framework/templates/feature/recap.md +0 -145
  276. package/framework/templates/project/validate.js +0 -97
  277. package/framework/templates/tasks.template.json +0 -17
  278. package/src/commands/state/usecase.js +0 -143
  279. package/src/commands/tasks/dag.js +0 -181
  280. package/src/dashboard/app/pages/Rubrics.jsx +0 -97
  281. package/src/dashboard/scanner/rubrics-scanner.js +0 -12
  282. package/src/lib/agents/output-collector.js +0 -76
  283. package/src/lib/evaluator-report.js +0 -91
  284. package/src/lib/specs/acervo-format.js +0 -70
  285. package/src/lib/specs/acervo-lifecycle.js +0 -264
  286. package/src/lib/specs/done-criteria-check.js +0 -200
  287. package/src/lib/specs/usecase-check.js +0 -301
  288. package/src/lib/specs/usecase-promoter.js +0 -301
  289. package/src/lib/specs/usecase-registry.js +0 -237
  290. package/src/lib/tasks/dispatch-planner.js +0 -319
  291. package/src/lib/tasks/mermaid-renderer.js +0 -132
  292. package/src/lib/tasks/task-classifier.js +0 -30
  293. package/src/lib/tasks/task-parser.js +0 -209
  294. package/src/lib/tasks/tasks-json-parser.js +0 -296
  295. package/src/lib/tasks/test-scope.js +0 -597
  296. package/src/lib/validators/architecture/architecture-validator.js +0 -270
  297. package/src/lib/validators/architecture/index.js +0 -7
  298. package/src/lib/validators/css/css-validator.js +0 -340
  299. package/src/lib/validators/css/index.js +0 -7
  300. package/src/lib/validators/design-system/design-system-validator.js +0 -237
  301. package/src/lib/validators/design-system/index.js +0 -7
  302. package/src/lib/validators/score-aggregator.js +0 -244
  303. package/src/lib/validators/ui/index.js +0 -7
  304. package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
@@ -15,10 +15,10 @@ import { loadTelemetryAllTrees, aggregateCost, dedupeEvents } from '../../lib/te
15
15
  import { isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
16
16
 
17
17
  /** Kinds a person follows by default; the rest are one click away. */
18
- export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'task', 'verify', 'gate', 'failure'];
18
+ export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'verify', 'gate', 'failure'];
19
19
  export const ALL_KINDS = [...DEFAULT_KINDS, 'write', 'read', 'command', 'tool'];
20
20
 
21
- const GATE_COMMAND = /\bmorph-spec\s+(approve|gate-check|gate-decision|advance|score|finish)\b/;
21
+ const GATE_COMMAND = /\bmorph-spec\s+(approve|gate-check|gate-decision|advance|finish)\b/;
22
22
  const MORPH_PERSONA = /^morph-(.+)$/;
23
23
  const SHELL_TOOLS = new Set(['Bash', 'PowerShell']);
24
24
  const SILENT_EVENTS = new Set(['ToolBatch', 'SessionUsage', 'AgentUsage']);
@@ -50,14 +50,14 @@ function nodesSummary(nodes) {
50
50
  * @returns {null | {
51
51
  * id: string, ts: string, feature: string|null, phase: string|null,
52
52
  * kind: string, title: string, detail: string|null, status: 'ok'|'fail'|null,
53
- * refs: { persona?: string, skill?: string, task?: string, path?: string },
53
+ * refs: { persona?: string, skill?: string, path?: string },
54
54
  * nodes?: Array<{name: string, status: string|null}>
55
55
  * }}
56
56
  */
57
57
  export function describeEvent(e) {
58
58
  if (!e || typeof e !== 'object' || SILENT_EVENTS.has(e.event)) return null;
59
59
  const base = {
60
- id: e.eventId || `${e.ts}:${e.event}:${e.tool || ''}:${e.task || ''}`,
60
+ id: e.eventId || `${e.ts}:${e.event}:${e.tool || ''}`,
61
61
  ts: e.ts || null,
62
62
  feature: e.feature || null,
63
63
  phase: e.phase || null,
@@ -78,18 +78,10 @@ export function describeEvent(e) {
78
78
  if (text.startsWith('<')) return null;
79
79
  return line('prompt', 'Você escreveu', { detail: clip(text) });
80
80
  }
81
- if (e.event === 'TaskStart') return line('task', `${e.task} começou`, { refs: { task: e.task } });
82
- if (e.event === 'TaskEnd') {
83
- const done = e.status === 'done';
84
- return line('task', done ? `${e.task} concluída` : `${e.task} terminou (${e.status ?? 'sem status'})`, {
85
- status: done ? 'ok' : null, refs: { task: e.task },
86
- });
87
- }
88
81
  if (e.event === 'Verify') {
89
82
  const ok = e.status === 'pass';
90
- const scope = e.task ? e.task : 'feature inteira';
91
- return line('verify', `verify ${scope} ${ok ? 'passou' : 'falhou'}`, {
92
- status: ok ? 'ok' : 'fail', nodes: nodesSummary(e.nodes), refs: e.task ? { task: e.task } : {},
83
+ return line('verify', `verify ${ok ? 'passou' : 'falhou'}`, {
84
+ status: ok ? 'ok' : 'fail', nodes: nodesSummary(e.nodes),
93
85
  detail: e.persisted === false && e.reason ? `não carimbou: ${e.reason}` : null,
94
86
  });
95
87
  }
@@ -344,8 +336,6 @@ export function featureCost(events, feature) {
344
336
  dispatches: c.dispatches,
345
337
  bytesTotal: c.bytesTotal,
346
338
  bytesByKind: c.bytesByKind,
347
- taskDurationSeconds: c.taskDurationSeconds,
348
- tasks: c.tasks,
349
339
  events: c.total,
350
340
  };
351
341
  }
@@ -13,7 +13,7 @@ const GENERIC_DISPATCH = 'morph-{id}';
13
13
  *
14
14
  * v5 has no phase list on the persona: a phase dispatches a persona when its
15
15
  * skill (`phases.json → skill`) names `morph-{personaId}` on a dispatch line
16
- * (morph-uiux → ui-designer, morph-plan → scout, morph-review → evaluator).
16
+ * (morph-uiux → ui-designer, morph-plan → scout, morph-review → verifier, reviewer, security).
17
17
  * A skill that dispatches the generic `morph-{id}` runs every implementer —
18
18
  * the same `isImplementer` the validator and the installer use.
19
19
  *
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * The reading material of the framework — the charter, the runtime
3
- * instructions, the CLI reference and the rubrics the evaluator scores with —
4
- * for the dashboard's Guide and Rubrics pages.
3
+ * instructions, the CLI reference and the Gate 3 review checklist — for the
4
+ * dashboard's Guide page.
5
5
  */
6
- import { readFileSync, readdirSync, existsSync } from 'fs';
6
+ import { readFileSync } from 'fs';
7
7
  import { join } from 'path';
8
8
 
9
9
  function read(path) {
@@ -23,26 +23,11 @@ export function headingsOf(content) {
23
23
  return out;
24
24
  }
25
25
 
26
- function titleOf(content, fallback) {
27
- const h = headingsOf(content || '').find((x) => x.depth === 1);
28
- return h?.text || fallback;
29
- }
30
-
31
- function mdDir(dir) {
32
- if (!existsSync(dir)) return [];
33
- return readdirSync(dir)
34
- .filter((f) => f.endsWith('.md'))
35
- .sort()
36
- .map((f) => {
37
- const content = read(join(dir, f)) || '';
38
- return { id: f.replace(/\.md$/, ''), file: f, title: titleOf(content, f.replace(/\.md$/, '')), content };
39
- });
40
- }
41
-
42
26
  const GUIDE = [
43
27
  { id: 'morph', path: 'MORPH.md', title: 'Carta do harness (MORPH.md)', summary: 'Como o Claude trabalha: papéis, fases, gates, sub-agents. É a fonte única do comportamento.' },
44
28
  { id: 'runtime', path: 'CLAUDE.md', title: 'Instruções de runtime (CLAUDE.md)', summary: 'O que é injetado no CLAUDE.md do projeto: como começar cada sessão, regras críticas, comandos.' },
45
29
  { id: 'cli', path: 'docs/CLI.md', title: 'Referência do CLI', summary: 'Todos os comandos morph-spec: gates, verify, worktrees, grafo, telemetria.' },
30
+ { id: 'review-checklist', path: 'evals/review-checklist.md', title: 'Checklist do Gate 3', summary: 'O que verificador, revisor e segurança procuram antes do ship: veredito SHIP/FIX e bloqueador com evidência.' },
46
31
  ];
47
32
 
48
33
  export function scanDocs(frameworkDir) {
@@ -51,8 +36,5 @@ export function scanDocs(frameworkDir) {
51
36
  return { ...g, content, headings: headingsOf(content).filter((h) => h.depth <= 3) };
52
37
  }).filter((g) => g.content);
53
38
 
54
- return {
55
- guide,
56
- rubrics: mdDir(join(frameworkDir, 'evals')).filter((d) => d.id === 'review-checklist'),
57
- };
39
+ return { guide };
58
40
  }
@@ -6,9 +6,9 @@
6
6
  * - which tree owns a feature → `resolveFeatureTree` (feature-truth.js)
7
7
  * - the current phase → `derivePhaseFrom` (state-reader.js)
8
8
  * - gate order → `GATE_PREREQ` (gate-order.js)
9
- * - the evaluator's verdict → `evaluatorVerdictFromText` (evaluator-report.js)
9
+ * - the review verdict → `reviewReportsFromDir` (review-reports.js)
10
10
  * - path confinement → `canonicalPath`/`isInside` (test-runner.js)
11
- * - tasks.json validity → `validateTasksJson` (tasks-json-parser.js)
11
+ * - slice progress → `sliceProgress` (slice-progress.js)
12
12
  *
13
13
  * Three places a feature can live: the root `.morph/features/{f}`, a worktree
14
14
  * `{worktrees}/{wt}/.morph/features/{f}`, and `.morph/archive/{f}` once
@@ -21,10 +21,12 @@
21
21
 
22
22
  import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
23
23
  import { join, basename, isAbsolute } from 'path';
24
- import { derivePhaseFrom, PHASE_ARTIFACTS } from '../../../framework/hooks/shared/state-reader.js';
24
+ import { derivePhaseFrom, diskArtifactProbe, PHASE_ARTIFACTS } from '../../../framework/hooks/shared/state-reader.js';
25
25
  import { GATE_PREREQ } from '../../core/state/gate-order.js';
26
- import { validateTasksJson } from '../../lib/tasks/tasks-json-parser.js';
27
- import { evaluatorVerdictFromText } from '../../lib/evaluator-report.js';
26
+ import { sliceProgress, checksPath } from '../../../framework/hooks/shared/slice-progress.js';
27
+ import {
28
+ reviewReportsFromDir, requiredReviewRoles, combinedVerdict, allBlockers, checksCoverage, reviewFileName, REVIEW_ROLES,
29
+ } from '../../lib/review-reports.js';
28
30
  import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
29
31
  import { containerDirs } from '../../../framework/hooks/shared/worktree-root.js';
30
32
  import { canonicalPath, isInside, readMorphConfig } from '../../lib/tasks/test-runner.js';
@@ -33,7 +35,6 @@ import { isFinished } from './feature-status.js';
33
35
  export const GATES = Object.keys(GATE_PREREQ);
34
36
  const FINISHED = new Set(['completed', 'abandoned']);
35
37
  const FOLDER_PHASE = Object.fromEntries(PHASE_ARTIFACTS.map(([folder, phase]) => [folder, phase]));
36
- const EVALUATOR_REPORT = '4-review/evaluator-report.md';
37
38
  const MAX_FILE_BYTES = 2 * 1024 * 1024;
38
39
  const MAX_DEPTH = 4;
39
40
 
@@ -141,31 +142,26 @@ function resolveLocation(projectDir, name, loc, resolveTree) {
141
142
  }
142
143
 
143
144
  /**
144
- * tasks.json as the dashboard needs it: the list even when the file breaks the
145
- * schema (a read-only viewer shows what is there), with the errors alongside.
145
+ * The plan's slices with the status their reports give, plus the fields the
146
+ * extraction carries for the card. `null` when the plan has not been checked yet.
146
147
  */
147
- export function readTasks(dir) {
148
- const path = join(dir, '2-plan', 'tasks.json');
149
- if (!existsSync(path)) return { list: [], errors: [], present: false };
150
- const data = readJson(path);
151
- if (!data) return { list: [], errors: ['tasks.json não é JSON válido'], present: true };
152
- const { valid, errors } = validateTasksJson(data);
153
- const list = Array.isArray(data.tasks) ? data.tasks.filter((t) => t && typeof t === 'object') : [];
154
- return { list, errors: valid ? [] : errors, present: true };
148
+ export function readSlices(dir) {
149
+ const progress = sliceProgress(dir);
150
+ if (!progress) return null;
151
+ const extracted = new Map((readJson(checksPath(dir))?.slices || []).filter((x) => x && x.id).map((x) => [x.id, x]));
152
+ const list = progress.slices.map((sl) => {
153
+ const x = extracted.get(sl.id) || {};
154
+ return {
155
+ ...sl,
156
+ persona: x.persona ?? null,
157
+ files: Array.isArray(x.files) ? x.files : [],
158
+ covers: Array.isArray(x.covers) ? x.covers : [],
159
+ };
160
+ });
161
+ return { counts: { total: progress.total, done: progress.done, partial: progress.partial, pending: progress.pending }, list };
155
162
  }
156
163
 
157
- function taskCounts(list) {
158
- const counts = { total: list.length, done: 0, inProgress: 0, blocked: 0, pending: 0, skipped: 0 };
159
- for (const t of list) {
160
- // `completed` is the pre-schema spelling of `done` (MORPH_OS archives carry both).
161
- if (t.status === 'done' || t.status === 'completed') counts.done += 1;
162
- else if (t.status === 'in_progress') counts.inProgress += 1;
163
- else if (t.status === 'blocked') counts.blocked += 1;
164
- else if (t.status === 'skipped') counts.skipped += 1;
165
- else counts.pending += 1;
166
- }
167
- return counts;
168
- }
164
+ const NO_SLICES = { total: 0, done: 0, partial: 0, pending: 0 };
169
165
 
170
166
  function gatesOf(feature) {
171
167
  const skipped = new Set(feature.skippedPhases || []);
@@ -185,20 +181,67 @@ function gatesOf(feature) {
185
181
  });
186
182
  }
187
183
 
188
- function evaluatorVerdict(dir) {
189
- const path = join(dir, ...EVALUATOR_REPORT.split('/'));
190
- if (!existsSync(path)) return null;
191
- try { return evaluatorVerdictFromText(readFileSync(path, 'utf8')); } catch { return null; }
184
+ // Security counts only when its report exists: deciding it is required takes git.
185
+ function reviewVerdict(dir, workType, reports = reviewReportsFromDir(dir)) {
186
+ const present = Object.values(reports).some((r) => r.status !== 'missing');
187
+ if (!present) return null;
188
+ const required = requiredReviewRoles({ workType, securityRequired: reports.security.status !== 'missing' });
189
+ return { verdict: combinedVerdict(reports, required), blockers: allBlockers(reports) };
190
+ }
191
+
192
+ /**
193
+ * One row per review report the feature asks for or already has: status,
194
+ * verdict, blockers and the SHA it reviewed.
195
+ */
196
+ export function reviewRows(reports, workType) {
197
+ const required = requiredReviewRoles({ workType, securityRequired: reports.security.status !== 'missing' });
198
+ return Object.keys(REVIEW_ROLES)
199
+ .filter((role) => required.includes(role) || reports[role].status !== 'missing')
200
+ .map((role) => {
201
+ const r = reports[role];
202
+ return {
203
+ role,
204
+ path: `4-review/${reviewFileName(role)}`,
205
+ status: r.status,
206
+ verdict: r.verdict ?? null,
207
+ blockers: r.blockers?.length ?? 0,
208
+ reviewedSha: r.reviewedSha ?? null,
209
+ };
210
+ });
211
+ }
212
+
213
+ /**
214
+ * The plan's scenarios, each with the verifier's row for it. `coverage` is
215
+ * `null` until a verifier report exists; only PROVADO covers.
216
+ */
217
+ export function readChecks(dir, verifier) {
218
+ const checks = readJson(checksPath(dir))?.checks;
219
+ if (!Array.isArray(checks)) return null;
220
+ const list = checks.filter((c) => c && c.id);
221
+ const rows = verifier?.checks ?? null;
222
+ const byId = new Map((rows ?? []).map((r) => [r.id, r]));
223
+ const coverage = verifier && verifier.status !== 'missing'
224
+ ? { total: list.length, ...checksCoverage(list.map((c) => c.id), rows) }
225
+ : null;
226
+ return {
227
+ coverage,
228
+ list: list.map((c) => ({
229
+ id: c.id,
230
+ text: c.text ?? '',
231
+ proof: c.proof ?? null,
232
+ status: byId.get(c.id)?.status ?? null,
233
+ evidence: byId.get(c.id)?.evidence ?? null,
234
+ })),
235
+ };
192
236
  }
193
237
 
194
238
  function summarize(name, resolved) {
195
239
  const { location, dir, ownerTree, rootStale } = resolved;
196
240
  const feature = normalizeFeature(resolved.feature);
197
- const has = (rel) => existsSync(join(dir, ...rel.split('/')));
198
241
  const gates = gatesOf(feature);
199
242
  const reviewApproved = gates.find((g) => g.gate === 'review')?.approved === true;
200
- const tasks = readTasks(dir);
201
- const verdict = evaluatorVerdict(dir);
243
+ const slices = readSlices(dir);
244
+ const verdict = reviewVerdict(dir, feature.workType);
202
245
  const status = feature.status || (location === 'archive' ? 'completed' : 'draft');
203
246
  const finished = isFinished({ status, location });
204
247
  return {
@@ -210,19 +253,19 @@ function summarize(name, resolved) {
210
253
  dir,
211
254
  ownerTree,
212
255
  rootStale,
213
- phase: derivePhaseFrom(feature, has),
214
- inEvaluation: has(EVALUATOR_REPORT) && !reviewApproved,
256
+ phase: derivePhaseFrom(feature, diskArtifactProbe(dir)),
257
+ inEvaluation: verdict !== null && !reviewApproved,
215
258
  gates,
216
259
  awaiting: !finished && gates.some((g) => g.paused),
217
- tasks: taskCounts(tasks.list),
218
- // Titles of an active feature's tasks, for the global search. Finished
260
+ slices: slices?.counts ?? NO_SLICES,
261
+ // Titles of an active feature's slices, for the global search. Finished
219
262
  // features leave them out: 170 archived plans would bloat every list reply.
220
- taskIndex: finished ? [] : tasks.list.map((t) => ({ id: t.id, title: t.title || '', status: t.status || 'pending' })),
263
+ sliceIndex: finished || !slices ? [] : slices.list.map((sl) => ({ id: sl.id, title: sl.title, status: sl.status })),
221
264
  verification: feature.verification
222
- ? { status: feature.verification.status ?? null, scope: feature.verification.scope ?? null, ts: feature.verification.ts ?? null }
265
+ ? { status: feature.verification.status ?? null, ts: feature.verification.ts ?? null }
223
266
  : null,
224
267
  verdict: verdict?.verdict ?? null,
225
- blockers: verdict?.verdict ? verdict.blockers.length : null,
268
+ blockers: verdict ? verdict.blockers.length : null,
226
269
  createdAt: feature.createdAt || null,
227
270
  updatedAt: feature.updatedAt || feature.archivedAt || feature.createdAt || null,
228
271
  archivedAt: feature.archivedAt || null,
@@ -309,24 +352,21 @@ export function scanFeatures(projectDir, { resolveTree = resolveFeatureTree } =
309
352
  };
310
353
  }
311
354
 
312
- /**
313
- * One feature in full: summary + tasks with their verify/score, the gate
314
- * decisions, the verify nodes and the document tree.
315
- */
316
355
  export function readFeatureDetail(projectDir, name, { resolveTree = resolveFeatureTree, known } = {}) {
317
356
  const resolved = resolveOne(projectDir, name, resolveTree, known);
318
357
  if (!resolved) return null;
319
358
  const summary = summarize(name, resolved);
320
359
  const feature = normalizeFeature(resolved.feature);
321
- const tasks = readTasks(resolved.dir);
322
- const verdict = evaluatorVerdict(resolved.dir);
360
+ const slices = readSlices(resolved.dir);
361
+ const reports = reviewReportsFromDir(resolved.dir);
362
+ const verdict = reviewVerdict(resolved.dir, feature.workType, reports);
363
+ const checks = readChecks(resolved.dir, reports.verifier);
323
364
  return {
324
365
  ...summary,
325
- tasksList: tasks.list.map((t) => ({
326
- ...t,
327
- verification: feature.taskVerifications?.[t.id] || null,
328
- })),
329
- tasksErrors: tasks.errors,
366
+ slicesList: slices?.list ?? [],
367
+ checksList: checks?.list ?? [],
368
+ checksCoverage: checks?.coverage ?? null,
369
+ reviews: reviewRows(reports, feature.workType),
330
370
  gateDecisions: feature.gateDecisions || {},
331
371
  verificationNodes: feature.verification?.nodes || null,
332
372
  featureVerification: feature.verifications?.feature || null,
@@ -336,13 +376,8 @@ export function readFeatureDetail(projectDir, name, { resolveTree = resolveFeatu
336
376
  };
337
377
  }
338
378
 
339
- /**
340
- * The content of one file of a feature, confined to the feature's directory.
341
- * Junctions and win32 case are resolved by `canonicalPath` before the check,
342
- * so `..`, an absolute path or a link that leaves the folder are all refused.
343
- *
344
- * @returns {{ path: string, content: string, mtime: string } | { error: string, status: number }}
345
- */
379
+ // Junctions and win32 case are resolved by `canonicalPath` before the check,
380
+ // so `..`, an absolute path or a link that leaves the feature folder are refused.
346
381
  export function readFeatureFile(projectDir, name, relPath, { resolveTree = resolveFeatureTree, known } = {}) {
347
382
  if (typeof relPath !== 'string' || relPath === '') return { error: 'path ausente', status: 400 };
348
383
  if (isAbsolute(relPath) || /^[a-zA-Z]:/.test(relPath)) return { error: 'path fora da feature', status: 403 };
@@ -4,7 +4,6 @@ import { scanHooks } from './hooks-scanner.js';
4
4
  import { scanSkills } from './skills-scanner.js';
5
5
  import { scanStandards } from './standards-scanner.js';
6
6
  import { scanRules } from './rules-scanner.js';
7
- import { scanRubrics } from './rubrics-scanner.js';
8
7
  import { scanDocs } from './docs-scanner.js';
9
8
 
10
9
  /**
@@ -21,7 +20,6 @@ export function scanAll(frameworkDir) {
21
20
  skills: scanSkills(frameworkDir, phases.list),
22
21
  standards: scanStandards(frameworkDir, agents.list),
23
22
  rules: scanRules(frameworkDir),
24
- rubrics: scanRubrics(frameworkDir),
25
23
  docs: scanDocs(frameworkDir),
26
24
  };
27
25
  }
@@ -89,7 +89,7 @@ export function createProjectApi(projectDir, deps = {}) {
89
89
 
90
90
  // Which tree owns a feature costs a few git calls (~0.3 s each on Windows)
91
91
  // and only changes when a file or folder appears or disappears — never when
92
- // an agent rewrites feature.json or tasks.json, which is what happens all
92
+ // an agent rewrites feature.json or a slice report, which is what happens all
93
93
  // the time. Remembered per feature; a structural change or TTL_MS forgets it.
94
94
  const resolveTree = deps.resolveTree || resolveFeatureTree;
95
95
  const now = deps.now || Date.now;
@@ -18,17 +18,22 @@ function parseFrontmatter(content) {
18
18
  return { paths, stacks };
19
19
  }
20
20
 
21
- /**
22
- * Standards a rule imports, each once. RulesMap keys an edge per ref, so a
23
- * rule that imports the same standard twice (infrastructure-standards.md did,
24
- * up to 8.38.0) drew two edges under one React key.
25
- */
21
+ const ROUTE_LINE = /^- \*\*([a-z0-9-]+)\*\* \(`([^`]+\.md)`\)/;
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.
25
+ export function parseStandardRoutes(content) {
26
+ const routes = [];
27
+ for (const line of content.split(/\r?\n/)) {
28
+ const m = ROUTE_LINE.exec(line);
29
+ if (m) routes.push({ id: m[1], path: m[2] });
30
+ }
31
+ return routes;
32
+ }
33
+
34
+ // RulesMap keys one edge per ref, so a repeated path would collide.
26
35
  export function extractStandardRefs(content) {
27
- const refs = new Set();
28
- const regex = /@\.morph\/framework\/standards\/(.+\.md)/g;
29
- let m;
30
- while ((m = regex.exec(content)) !== null) refs.add(m[1]);
31
- return [...refs];
36
+ return [...new Set(parseStandardRoutes(content).map((r) => r.path))];
32
37
  }
33
38
 
34
39
  export function scanRules(frameworkDir) {
@@ -84,13 +84,12 @@ export function scanSkills(frameworkDir, phases = []) {
84
84
  if (!existsSync(skillPath)) continue;
85
85
  const content = readFileSync(skillPath, 'utf8');
86
86
  const fm = parseFrontmatter(content);
87
- // The framework spells it `user-invokable`; the Claude Code docs say `user-invocable`.
88
- const invocable = fm['user-invokable'] ?? fm['user-invocable'];
87
+ const invocable = fm['user-invocable'];
89
88
  skills.push({
90
89
  id: skillDir.name,
91
90
  name: fm.name || skillDir.name,
92
91
  description: fm.description || '',
93
- whenToInvoke: fm['when-to-invoke'] || '',
92
+ whenToUse: fm.when_to_use || '',
94
93
  level: levelDir.name,
95
94
  userInvocable: invocable === true || invocable === 'true',
96
95
  stacks: typeof fm.stacks === 'string' ? fm.stacks.replace(/[[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean) : [],
@@ -1,11 +1,12 @@
1
1
  import { readFileSync, existsSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { personaStandardIds } from '../../utils/agents-installer.js';
4
+ import { loadStandardsRegistry } from '../../lib/standards/registry.js';
4
5
 
5
6
  export function scanStandards(frameworkDir, agents) {
6
7
  const standardsJsonPath = join(frameworkDir, 'standards', 'STANDARDS.json');
7
8
  if (!existsSync(standardsJsonPath)) return { version: 'unknown', list: [] };
8
- const sJson = JSON.parse(readFileSync(standardsJsonPath, 'utf8'));
9
+ const sJson = loadStandardsRegistry(standardsJsonPath);
9
10
  const refMap = {};
10
11
  for (const agent of agents) {
11
12
  for (const std of personaStandardIds(agent)) {
@@ -18,7 +18,7 @@ export class CiUsageError extends Error {}
18
18
  const NODE_KEYS = ['build', 'tests', 'validators', 'e2e'];
19
19
 
20
20
  function isVerifyResult(result) {
21
- return Boolean(result) && ['pass', 'fail'].includes(result.status) && result.scope === 'feature'
21
+ return Boolean(result) && ['pass', 'fail'].includes(result.status)
22
22
  && NODE_KEYS.every((k) => typeof result.nodes?.[k]?.status === 'string');
23
23
  }
24
24
 
@@ -47,6 +47,22 @@ function dispatchWorkflow(root, workflow, branch) {
47
47
  }
48
48
  }
49
49
 
50
+ /**
51
+ * When CI falls back to local, the last green feature stamp still proves the tree if nothing moved
52
+ * since it by the gate's own staleness rule and nothing is uncommitted (treeMovedSince sees only
53
+ * commits). A null sha or HEAD would read as "not moved", so both are required.
54
+ *
55
+ * @returns {object|null} the stamp to reuse, or null = run local
56
+ */
57
+ export function reusableLocalStamp(root, stamp) {
58
+ if (stamp?.status !== 'pass' || !stamp.sha) return null;
59
+ const head = getHeadSha(root);
60
+ if (!head) return null;
61
+ const dirty = uncommittedCode(root);
62
+ if (dirty === null || dirty.length > 0) return null;
63
+ return treeMovedSince(root, stamp.sha, head) ? null : stamp;
64
+ }
65
+
50
66
  /**
51
67
  * `verify {f} --ci`: the feature-scope proof, run by the project's CI over the pushed commit.
52
68
  * Every way CI cannot answer is `mode: 'local'` with the reason, and the caller runs the local
@@ -140,7 +140,7 @@ export function listRuns(root, branch, workflow) {
140
140
 
141
141
  /**
142
142
  * The newest run over the same code as HEAD. `isCurrent` is a tree comparison, not SHA equality:
143
- * the evaluator and the archive commit only `.morph/`, and the workflow does not re-run for them.
143
+ * the review agents and the archive commit only `.morph/`, and the workflow does not re-run for them.
144
144
  */
145
145
  export function pickRun(runs, isCurrent) {
146
146
  return (runs || []).find((run) => !(run.status === 'completed' && NO_VERDICT.has(run.conclusion))
@@ -10,14 +10,9 @@
10
10
  import { existsSync, readFileSync, readdirSync } from 'fs';
11
11
  import { join } from 'path';
12
12
 
13
- /**
14
- * Common monorepo locations for a frontend `package.json`, root included.
15
- *
16
- * Exported so `discoverFrontendPackages()` (src/lib/tasks/test-nodes.js, #87
17
- * PR B) scans the SAME candidate list this detector already uses — one
18
- * spelling of "where a frontend package might live", not two that drift.
19
- */
20
- export const PACKAGE_LOCATIONS = ['.', 'src/frontend', 'src/web', 'frontend', 'apps/web', 'packages/app'];
13
+ import { PACKAGE_LOCATIONS } from '../../../framework/hooks/shared/frontend-locations.js';
14
+
15
+ export { PACKAGE_LOCATIONS };
21
16
 
22
17
  /**
23
18
  * Detect project structure from filesystem evidence.