@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
@@ -1,181 +0,0 @@
1
- import chalk from 'chalk';
2
- import { existsSync, readFileSync } from 'fs';
3
- import { join } from 'path';
4
- import { parseTasksJsonLenient, oversizedTasks, MAX_TASK_OUTPUTS } from '../../lib/tasks/tasks-json-parser.js';
5
- import { planDispatch } from '../../lib/tasks/dispatch-planner.js';
6
- import { renderMermaidFlowchart } from '../../lib/tasks/mermaid-renderer.js';
7
- import { checkUseCases } from '../../lib/specs/usecase-check.js';
8
- import { checkArtifactCriteria } from '../../lib/specs/done-criteria-check.js';
9
- import { checkLifecycle } from '../../lib/specs/acervo-lifecycle.js';
10
-
11
- /** Shared by `dag` and `usecase check` — the two surfaces of the same finding. */
12
- export function printLifecycleWarnings(lifecycle) {
13
- if (!lifecycle?.warnings?.length) return;
14
- const n = lifecycle.warnings.length;
15
- console.error(chalk.yellow(`\n⚠ Ciclo de vida do acervo — ${n} ${n === 1 ? 'aviso' : 'avisos'}`));
16
- for (const w of lifecycle.warnings) console.error(chalk.yellow(` ${w}`));
17
- }
18
-
19
- function planGateApproved(featureDir) {
20
- try {
21
- const feature = JSON.parse(readFileSync(join(featureDir, 'feature.json'), 'utf8'));
22
- return feature?.approvalGates?.plan?.approved === true;
23
- } catch {
24
- return false;
25
- }
26
- }
27
-
28
- /**
29
- * `morph-spec dag <feature>` — analyze the tasks.json DAG and print the
30
- * sequential vs parallel dispatch decision, weighted by effort.
31
- *
32
- * This is the command morph-apply §2 runs instead of re-deriving the rules
33
- * (and instead of the old inline JS snippets the LLM could not execute).
34
- *
35
- * `--mermaid` emits the DAG as a Mermaid `flowchart TD` instead (for
36
- * visualizing the plan at Gate 2), bypassing the dispatch analysis entirely.
37
- */
38
- export async function dagCommand(featureName, options = {}) {
39
- try {
40
- const featureDir = join(process.cwd(), '.morph', 'features', featureName);
41
- const planDir = join(featureDir, '2-plan');
42
- const tasksPath = join(planDir, 'tasks.json');
43
- if (!existsSync(tasksPath)) {
44
- console.error(chalk.red(`\n❌ tasks.json not found for feature "${featureName}"`));
45
- console.error(chalk.gray(`Expected at: .morph/features/${featureName}/2-plan/tasks.json\n`));
46
- process.exit(1);
47
- }
48
-
49
- // parseTasksJsonLenient validates the schema and throws with the error
50
- // list, but defaults a missing `version` (legacy plans predating the
51
- // field) instead of failing — the DAG analysis doesn't depend on it.
52
- const { data, versionDefaulted } = await parseTasksJsonLenient(tasksPath);
53
- if (versionDefaulted) {
54
- console.error(chalk.yellow(
55
- `⚠ tasks.json has no 'version' field — defaulting to "1.0" for this analysis.\n` +
56
- ` Consider adding it back explicitly (safe: protect-spec-files allows backfilling it).`
57
- ));
58
- }
59
-
60
- // --mermaid short-circuits before the dispatch analysis: visualizing the
61
- // plan's DAG doesn't need a sequential-vs-parallel decision.
62
- if (options.mermaid) {
63
- console.log(renderMermaidFlowchart(data));
64
- return;
65
- }
66
-
67
- // Use-case cross-check. Runs AFTER the --mermaid short-circuit on purpose:
68
- // rendering the DAG is pure visualization and must never be blocked by the
69
- // state of the spec. A broken `usecase` reference IS fatal here — the file
70
- // either exists or it does not, so there is no false positive to weigh
71
- // against a plan that points at a use case nobody can read.
72
- const ucCheck = checkUseCases(data, join(planDir, 'usecases'));
73
-
74
- // Artifact-postcondition warning (issue #87). Lexical, not wiring — it
75
- // runs over ALL tasks even when the feature has no use cases at all
76
- // (checkUseCases short-circuits above in that case), because a task's
77
- // own doneCriteria can describe an artifact with no use case in sight.
78
- const artifactCriteria = checkArtifactCriteria(data, join(planDir, 'usecases'));
79
-
80
- // Acervo lifecycle: a task citing a superseded use case, and the spec.md
81
- // revocation rows the finish would fail to apply. Warnings only.
82
- const lifecycle = checkLifecycle({ cwd: process.cwd(), featureDir, tasksData: data });
83
-
84
- // Task acima do teto não cabe num dispatch. Antes do Gate 2 é erro (o plano
85
- // ainda fatia); depois é aviso, para não travar feature em voo.
86
- const oversized = oversizedTasks(data);
87
- const sizeBlocks = oversized.length > 0 && !planGateApproved(featureDir);
88
- const taskSize = { max: MAX_TASK_OUTPUTS, oversized, blocking: sizeBlocks };
89
-
90
- const plan = planDispatch(data);
91
-
92
- if (options.json) {
93
- console.log(JSON.stringify({ ...plan, useCases: ucCheck, artifactCriteria, lifecycle, taskSize }, null, 2));
94
- if (ucCheck.errors.length || sizeBlocks) process.exit(1);
95
- return;
96
- }
97
-
98
- const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
99
-
100
- if (ucCheck.errors.length) {
101
- console.error(chalk.red(`\n❌ Use cases — ${plural(ucCheck.errors.length, 'erro', 'erros')}\n`));
102
- for (const e of ucCheck.errors) console.error(chalk.red(` ${e}`));
103
- console.error(chalk.gray('\n Corrija a referência no tasks.json ou restaure o use case.\n'));
104
- process.exit(1);
105
- }
106
- if (oversized.length) {
107
- const tone = sizeBlocks ? chalk.red : chalk.yellow;
108
- const head = sizeBlocks ? '❌' : '⚠';
109
- console.error(tone(`\n${head} Tamanho de task — ${plural(oversized.length, 'task', 'tasks')} acima de ${MAX_TASK_OUTPUTS} outputs`));
110
- for (const t of oversized) console.error(tone(` ${t.id}: ${t.outputs} outputs`));
111
- console.error(chalk.gray(' Fatie por pós-condição: uma task cabe num dispatch (morph-plan, "Tamanho da task").\n'));
112
- if (sizeBlocks) process.exit(1);
113
- }
114
- if (ucCheck.warnings.length) {
115
- console.error(chalk.yellow(`\n⚠ Use cases — ${plural(ucCheck.warnings.length, 'aviso', 'avisos')}`));
116
- for (const w of ucCheck.warnings) console.error(chalk.yellow(` ${w}`));
117
- }
118
- if (artifactCriteria.warnings.length) {
119
- console.error(chalk.yellow(`\n⚠ Critérios de artefato — ${plural(artifactCriteria.warnings.length, 'aviso', 'avisos')}`));
120
- for (const w of artifactCriteria.warnings) console.error(chalk.yellow(` ${w}`));
121
- console.error(chalk.gray(' Teste que lê texto de arquivo não prova comportamento (morph-plan §2, "Por que artefato").'));
122
- }
123
- printLifecycleWarnings(lifecycle);
124
-
125
- console.log(chalk.bold(`\n📊 DAG — ${featureName}`));
126
- console.log('━'.repeat(60));
127
-
128
- const { stats } = plan;
129
- console.log(`\nTasks: ${stats.total} total, ${stats.pending} pendente(s)`);
130
- const groupLine = Object.keys(stats.groups)
131
- .map(g => `${g}:${stats.groups[g]} (peso ${stats.weights[g]})`)
132
- .join(' | ');
133
- if (groupLine) console.log(chalk.gray(`Grupos pendentes: ${groupLine}`));
134
-
135
- const modeLabel = {
136
- 'sequential': 'SEQUENCIAL (auto-advance)',
137
- 'parallel-flows': 'PARALELO por fluxo de domínio',
138
- 'parallel-roots': 'PARALELO por raízes independentes',
139
- }[plan.mode];
140
- console.log(chalk.cyan(`\nDecisão: ${modeLabel}`));
141
- console.log(` → ${plan.reason}`);
142
-
143
- for (const flow of plan.flows) {
144
- console.log(` ${chalk.green('▶')} Fluxo ${chalk.bold(flow.group)} (peso ${flow.weight}): ${flow.taskIds.join(', ')}`);
145
- }
146
- if (plan.batch.length > 0) {
147
- console.log(` ${chalk.green('▶')} Batch paralelo: ${plan.batch.join(', ')}`);
148
- }
149
- if (plan.inline.length > 0) {
150
- console.log(` ${chalk.yellow('○')} Inline (sem dispatch próprio): ${plan.inline.join(', ')}`);
151
- }
152
- if (plan.sequentialRemainder.length > 0) {
153
- console.log(` ${chalk.gray('→')} Sequencial: ${plan.sequentialRemainder.join(', ')}`);
154
- }
155
-
156
- // Isolamento — only meaningful when there IS a parallel batch. In a shared
157
- // working tree two agents writing the same path corrupt each other; the
158
- // dag recommends `isolation: "worktree"` so each runs in its own worktree
159
- // (morph-apply §3b-w).
160
- const iso = plan.isolation;
161
- if (iso && (iso.recommended === 'worktree' || iso.unknownFootprint.length > 0)) {
162
- console.log(chalk.bold('\nIsolamento:'));
163
- if (iso.recommended === 'worktree') {
164
- console.log(` ${chalk.yellow('⚠')} recomendado: ${chalk.bold('worktree')} — ${iso.reason}`);
165
- for (const c of iso.collisions) {
166
- console.log(chalk.gray(` colisão em "${c.path}": ${c.units.join(' ‖ ')} (${c.tasks.join(', ')})`));
167
- }
168
- } else {
169
- console.log(` ${chalk.gray('○')} ${iso.reason}`);
170
- }
171
- if (iso.unknownFootprint.length > 0) {
172
- console.log(chalk.gray(` footprint desconhecido (sem outputs): ${iso.unknownFootprint.join(', ')}`));
173
- }
174
- }
175
-
176
- console.log('\n' + '━'.repeat(60) + '\n');
177
- } catch (error) {
178
- console.error(chalk.red(`\n❌ Error analyzing DAG: ${error.message}\n`));
179
- process.exit(1);
180
- }
181
- }
@@ -1,97 +0,0 @@
1
- import morphData from 'virtual:morph-data';
2
- import KnowledgeMarkdown from '../components/KnowledgeMarkdown.jsx';
3
- import { useSelection } from '../components/EntityLink.jsx';
4
-
5
- const DOCS = morphData.docs || { rubrics: [] };
6
- const CHECKLIST = DOCS.rubrics.find((r) => r.id === 'review-checklist');
7
- const VALIDATORS = morphData.rubrics || [];
8
-
9
- const GROUPS = [
10
- { title: 'Avaliador do Gate 3', hint: 'veredito e bloqueadores com evidência', items: CHECKLIST ? [{ key: 'dim:review-checklist', title: CHECKLIST.title, doc: CHECKLIST }] : [] },
11
- { title: 'Validadores determinísticos', hint: 'pesos por categoria no verify', items: VALIDATORS.map((v) => ({ key: `val:${v.id}`, title: `Rubrica ${v.id}`, validator: v })) },
12
- ].filter((g) => g.items.length);
13
-
14
- const ALL = GROUPS.flatMap((g) => g.items);
15
-
16
- function ValidatorRubric({ v }) {
17
- return (
18
- <article className="prose-morph">
19
- <h1>Rubrica {v.id}</h1>
20
- {v.description && <p>{v.description}</p>}
21
- <table>
22
- <thead><tr><th>Categoria</th><th>Peso</th><th>Validadores</th><th>O que mede</th></tr></thead>
23
- <tbody>
24
- {Object.entries(v.categories).map(([name, c]) => (
25
- <tr key={name}>
26
- <td>{name}</td>
27
- <td className="font-mono">{c.weight ?? '—'}</td>
28
- <td className="font-mono text-xs">{(c.validators || []).join(', ') || '—'}</td>
29
- <td>{c.description || ''}</td>
30
- </tr>
31
- ))}
32
- </tbody>
33
- </table>
34
- <p>O score é informativo: o nó de validadores do verify reprova por erro de validador, não pelo número.</p>
35
- </article>
36
- );
37
- }
38
-
39
- export function RubricsView({ sel, onSel }) {
40
- const current = ALL.find((i) => i.key === sel) || null;
41
- return (
42
- <div className="mx-auto max-w-7xl px-4 pb-24 pt-10 md:px-10 md:pt-14">
43
- <header className="rise mb-8">
44
- <h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Rubricas</h1>
45
- <p className="mt-3 max-w-2xl text-sm leading-relaxed text-[var(--color-dim)]">
46
- Com o que o trabalho é julgado. O avaliador independente decide SHIP ou FIX, e só bloqueia com evidência de UC, teste, regressão ou segurança.
47
- Os validadores rodam no <span className="font-mono text-[var(--color-fg)]">morph-spec verify</span>, sem modelo.
48
- </p>
49
- </header>
50
- <div className="rise-2 grid gap-6 lg:grid-cols-[17rem_minmax(0,1fr)]">
51
- <nav aria-label="Rubricas" className="lg:sticky lg:top-4 lg:self-start">
52
- <button
53
- type="button"
54
- onClick={() => onSel(null)}
55
- aria-current={!current ? 'page' : undefined}
56
- className={['fluid mb-4 block w-full rounded-lg px-3 py-2 text-left text-sm transition-colors', !current ? 'bg-white/[0.08] text-[var(--color-fg)]' : 'text-[var(--color-dim)] hover:bg-white/[0.04]'].join(' ')}
57
- >
58
- Como o Gate 3 decide
59
- </button>
60
- {GROUPS.map((g) => (
61
- <div key={g.title} className="mb-5">
62
- <p className="px-3 text-xs text-[var(--color-faint)]">{g.title}</p>
63
- <p className="mb-1.5 px-3 text-[11px] text-[var(--color-faint)]">{g.hint}</p>
64
- <ul>
65
- {g.items.map((i) => (
66
- <li key={i.key}>
67
- <button
68
- type="button"
69
- onClick={() => onSel(i.key)}
70
- aria-current={current?.key === i.key ? 'page' : undefined}
71
- className={['fluid block w-full truncate rounded-lg px-3 py-1.5 text-left text-sm transition-colors', current?.key === i.key ? 'bg-white/[0.08] text-[var(--color-fg)]' : 'text-[var(--color-dim)] hover:bg-white/[0.04] hover:text-[var(--color-fg)]'].join(' ')}
72
- title={i.title}
73
- >
74
- {i.title}
75
- </button>
76
- </li>
77
- ))}
78
- </ul>
79
- </div>
80
- ))}
81
- </nav>
82
- <div className="bezel min-w-0">
83
- <div className="bezel-core min-w-0 px-6 py-8 md:px-12 md:py-10">
84
- {!current && (CHECKLIST ? <KnowledgeMarkdown text={CHECKLIST.content} /> : <p className="text-sm text-[var(--color-dim)]">Escolha uma rubrica.</p>)}
85
- {current?.doc && <KnowledgeMarkdown text={current.doc.content} />}
86
- {current?.validator && <ValidatorRubric v={current.validator} />}
87
- </div>
88
- </div>
89
- </div>
90
- </div>
91
- );
92
- }
93
-
94
- export default function Rubrics() {
95
- const [sel, setSel] = useSelection();
96
- return <RubricsView sel={sel} onSel={setSel} />;
97
- }
@@ -1,12 +0,0 @@
1
- import { readFileSync, readdirSync, existsSync } from 'fs';
2
- import { join } from 'path';
3
-
4
- export function scanRubrics(frameworkDir) {
5
- const dir = join(frameworkDir, 'rubrics');
6
- if (!existsSync(dir)) return [];
7
- return readdirSync(dir).filter(f => f.endsWith('.json')).map(f => {
8
- const d = JSON.parse(readFileSync(join(dir, f), 'utf8'));
9
- return { id: d.id, version: d.version || '1.0.0', description: d.description || '',
10
- categories: d.categories || {}, thresholds: d.thresholds || { approve: 9.0, retry: 6.0 } };
11
- });
12
- }
@@ -1,76 +0,0 @@
1
- /**
2
- * output-collector — Collect phase output files for multi-source agent detection
3
- *
4
- * Reads all existing phase outputs for a feature and returns them as
5
- * weighted TextSource[] for use in detectAgents().
6
- */
7
-
8
- import { readFileSync, existsSync } from 'fs';
9
- import { OUTPUT_SCHEMA, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
10
-
11
- /**
12
- * Default source weights per output type.
13
- * Keys match OUTPUT_SCHEMA keys (camelCase).
14
- */
15
- export const SOURCE_WEIGHTS = {
16
- proposal: 1.0,
17
- stackScan: 0.8,
18
- spec: 1.5,
19
- tasks: 1.3,
20
- decisions: 1.0,
21
- uiDesignSystem: 1.0,
22
- uiMockups: 1.0,
23
- uiComponents: 1.0,
24
- uiFlows: 1.0,
25
- recap: 1.0,
26
- reviewReport: 1.0,
27
- };
28
-
29
- /** Phase order for sorting collected outputs (v5 NLH model) */
30
- const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
31
-
32
- /**
33
- * Collect all existing phase output files for a feature.
34
- *
35
- * @param {string} projectRoot - Absolute path to project root
36
- * @param {string} featureName - Feature name
37
- * @returns {Array<{source: string, content: string, weight: number}>}
38
- */
39
- export function collectPhaseOutputs(projectRoot, featureName) {
40
- const results = [];
41
-
42
- for (const [key, entry] of Object.entries(OUTPUT_SCHEMA)) {
43
- // Multi-file types resolve to a DIRECTORY — there is no single document to
44
- // weigh as one source. Skipped explicitly rather than left to fail as an
45
- // EISDIR swallowed by the catch below.
46
- if (entry.multi) continue;
47
-
48
- const absPath = getAbsoluteOutputPath(projectRoot, featureName, key);
49
- if (!existsSync(absPath)) continue;
50
-
51
- try {
52
- const content = readFileSync(absPath, 'utf8');
53
- if (!content.trim()) continue; // Skip empty files
54
-
55
- results.push({
56
- source: entry.filename,
57
- content,
58
- weight: SOURCE_WEIGHTS[key] ?? 1.0,
59
- _phase: entry.phase,
60
- });
61
- } catch {
62
- // Skip unreadable files — fail-open
63
- continue;
64
- }
65
- }
66
-
67
- // Sort by phase order
68
- results.sort((a, b) => {
69
- const orderA = PHASE_ORDER.indexOf(a._phase);
70
- const orderB = PHASE_ORDER.indexOf(b._phase);
71
- return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB);
72
- });
73
-
74
- // Remove internal _phase field
75
- return results.map(({ source, content, weight }) => ({ source, content, weight }));
76
- }
@@ -1,91 +0,0 @@
1
- import { existsSync, readFileSync } from 'fs';
2
- import { getAbsoluteOutputPath } from '../core/paths/output-schema.js';
3
-
4
- export const VERDICT_TOKEN = '**Verdict:**';
5
- export const VERDICTS = ['SHIP', 'FIX'];
6
- export const BLOCKER_TYPES = ['uc', 'teste', 'regressao', 'seguranca'];
7
-
8
- // O relatório é append-only: uma reavaliação acrescenta ao fim, e o token vigente é o ÚLTIMO.
9
- const VERDICT_RE = /\*\*Verdict:\*\*\s*([A-Za-z]+)/g;
10
- const BLOCKERS_SECTION_RE = /^##\s+Bloqueadores\s*$/gim;
11
- const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
12
- const SCREEN_CHECKLIST_SECTION_RE = /^##\s+Checklist\s+de\s+tela\s*$/gim;
13
-
14
- export function evaluatorReportPath(cwd, featureName) {
15
- return getAbsoluteOutputPath(cwd, featureName, 'evaluatorReport');
16
- }
17
-
18
- function readReport(cwd, featureName) {
19
- try {
20
- const p = evaluatorReportPath(cwd, featureName);
21
- return existsSync(p) ? readFileSync(p, 'utf8') : null;
22
- } catch {
23
- return null;
24
- }
25
- }
26
-
27
- function lastSectionBody(text, re) {
28
- const matches = [...text.matchAll(re)];
29
- if (matches.length === 0) return null;
30
- const last = matches[matches.length - 1];
31
- const rest = text.slice(last.index + last[0].length);
32
- const next = rest.search(/^#{1,2}\s/m);
33
- return next === -1 ? rest : rest.slice(0, next);
34
- }
35
-
36
- const normalizeType = (s) => s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').trim();
37
-
38
- /**
39
- * Linhas de tabela da última `## Bloqueadores`: `| id | tipo | onde | evidência |`.
40
- * Cabeçalho e separador não contam; `untyped` são as linhas cujo tipo não é de {@link BLOCKER_TYPES}.
41
- */
42
- function parseBlockers(body) {
43
- const blockers = [];
44
- const untyped = [];
45
- for (const line of body.split(/\r?\n/)) {
46
- if (!/^\s*\|/.test(line)) continue;
47
- const cells = line.trim().replace(/^\||\|$/g, '').split('|').map((c) => c.trim());
48
- if (cells.every((c) => /^:?-{2,}:?$/.test(c) || c === '')) continue;
49
- if (/^#$|^id$/i.test(cells[0]) && /^tipo$/i.test(cells[1] ?? '')) continue;
50
- const row = { id: cells[0], type: normalizeType(cells[1] ?? ''), where: cells[2] ?? '', evidence: cells[3] ?? '' };
51
- (BLOCKER_TYPES.includes(row.type) ? blockers : untyped).push(row);
52
- }
53
- return { blockers, untyped };
54
- }
55
-
56
- export function evaluatorVerdictFromText(text) {
57
- if (typeof text !== 'string') return { verdict: undefined, blockers: [], untyped: [], superseded: [] };
58
- const verdicts = [...text.matchAll(VERDICT_RE)].map((m) => m[1].toUpperCase());
59
- const body = lastSectionBody(text, BLOCKERS_SECTION_RE);
60
- const { blockers, untyped } = body === null ? { blockers: [], untyped: [] } : parseBlockers(body);
61
- return {
62
- verdict: verdicts.length ? verdicts[verdicts.length - 1] : undefined,
63
- blockers,
64
- untyped,
65
- superseded: verdicts.slice(0, -1),
66
- };
67
- }
68
-
69
- /**
70
- * `missing`: sem relatório. `unverdicted`: relatório sem `**Verdict:** SHIP|FIX` válido.
71
- * `ok`: veredito legível — quem decide se ele libera o ship é o risk-detector.
72
- */
73
- export function evaluatorReportState(cwd, featureName) {
74
- const path = evaluatorReportPath(cwd, featureName);
75
- const text = readReport(cwd, featureName);
76
- if (text === null) return { status: 'missing', path };
77
- const parsed = evaluatorVerdictFromText(text);
78
- if (!VERDICTS.includes(parsed.verdict)) return { status: 'unverdicted', path };
79
- return { status: 'ok', path, ...parsed };
80
- }
81
-
82
- export function evaluatorMediaSection(cwd, featureName) {
83
- const text = readReport(cwd, featureName);
84
- return text !== null && MEDIA_SECTION_RE.test(text);
85
- }
86
-
87
- export function evaluatorScreenChecklist(cwd, featureName) {
88
- const text = readReport(cwd, featureName);
89
- const body = text === null ? null : lastSectionBody(text, SCREEN_CHECKLIST_SECTION_RE);
90
- return body === null ? { section: false, body: '' } : { section: true, body };
91
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * The shapes every reader and writer of the acervo agrees on — one copy each.
3
- * A second copy of any of these is how two readers start disagreeing about the
4
- * same file: a use case one of them sees and the other does not, a table row
5
- * one of them splits differently.
6
- */
7
-
8
- import { existsSync, readdirSync } from 'fs';
9
- import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
10
-
11
- /** A use-case id: `UC-07`. */
12
- export const UC_ID_RE = /^UC-\d+$/;
13
-
14
- /** A use case no longer holds as the system's behaviour (or holds only in its `scope`). */
15
- export const SUPERSEDED = 'superseded';
16
-
17
- const UC_FILENAME_RE = new RegExp(OUTPUT_SCHEMA.usecases.filenamePattern);
18
-
19
- /** `UC-07-listar-veterinarios.md` → `UC-07`. Null when the name isn't a use case. */
20
- export function useCaseId(filename) {
21
- const m = /^(UC-\d+)-/.exec(filename);
22
- return m ? m[1] : null;
23
- }
24
-
25
- /** The name the promotion carries: `UC-{nn}-{slug}.md`, slug in lowercase kebab. */
26
- export const isCanonicalUseCaseName = (name) => UC_FILENAME_RE.test(name);
27
-
28
- /**
29
- * Every use case in `dir`, grouped by id. By default only the canonical names
30
- * — the ones the promotion ever carries. `canonicalOnly: false` also lists any
31
- * `UC-{nn}-….md`, for the plan-time check that has always accepted them and
32
- * now warns instead. An id with more than one file is a broken acervo, and
33
- * readers must be able to see it rather than silently pick one.
34
- *
35
- * @param {string} dir
36
- * @param {{ canonicalOnly?: boolean }} [options]
37
- * @returns {Map<string, string[]>} id → filenames, sorted; empty when the folder is missing or unreadable
38
- */
39
- export function useCaseFilesById(dir, { canonicalOnly = true } = {}) {
40
- const byId = new Map();
41
- try {
42
- if (!existsSync(dir)) return byId;
43
- for (const name of readdirSync(dir).sort()) {
44
- const id = useCaseId(name);
45
- if (!id || !name.endsWith('.md')) continue;
46
- if (canonicalOnly && !isCanonicalUseCaseName(name)) continue;
47
- if (!byId.has(id)) byId.set(id, []);
48
- byId.get(id).push(name);
49
- }
50
- } catch { /* unreadable — nothing to see */ }
51
- return byId;
52
- }
53
-
54
- /** A markdown table separator row: `|---|:--:|`, with or without the outer pipes. */
55
- export const SEPARATOR_ROW_RE = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
56
-
57
- /** A line that belongs to a table: it carries an unescaped pipe (GFM does not require the leading one). */
58
- export const isTableLine = (line) => /(?<!\\)\|/.test(line);
59
-
60
- /**
61
- * Split a table row on unescaped pipes, outer pipes optional; `\|` inside a
62
- * cell is a literal pipe.
63
- *
64
- * @param {string} row
65
- * @returns {string[]} trimmed cells
66
- */
67
- export function tableCells(row) {
68
- const inner = row.trim().replace(/^\|/, '').replace(/(?<!\\)\|$/, '');
69
- return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
70
- }