@polymorphism-tech/morph-spec 9.1.0 โ†’ 9.3.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 (104) hide show
  1. package/CLAUDE.md +14 -78
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +14 -1
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +4 -3
  8. package/framework/MORPH.md +7 -10
  9. package/framework/agents.json +15 -12
  10. package/framework/commands/morph-status.md +2 -2
  11. package/framework/docs/CLI.md +24 -16
  12. package/framework/evals/review-checklist.md +15 -2
  13. package/framework/hooks/README.md +8 -44
  14. package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
  15. package/framework/hooks/claude-code/core/state-sync.js +1 -1
  16. package/framework/hooks/claude-code/core/worktree-context.js +23 -0
  17. package/framework/hooks/claude-code/statusline.py +3 -2
  18. package/framework/hooks/claude-code/statusline.sh +11 -11
  19. package/framework/hooks/shared/gate-event.js +18 -0
  20. package/framework/hooks/shared/next-step.js +107 -0
  21. package/framework/hooks/shared/slice-progress.js +18 -5
  22. package/framework/hooks/shared/state-writer.js +6 -1
  23. package/framework/mcp/registry.json +31 -1
  24. package/framework/rules/nextjs-standards.md +1 -1
  25. package/framework/schemas/feature.schema.json +1 -0
  26. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  27. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  28. package/framework/skills/core/morph-apply/SKILL.md +6 -4
  29. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  30. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  31. package/framework/skills/core/morph-media/SKILL.md +1 -1
  32. package/framework/skills/core/morph-plan/SKILL.md +8 -6
  33. package/framework/skills/core/morph-review/SKILL.md +9 -6
  34. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  35. package/framework/skills/core/morph-uiux/SKILL.md +2 -1
  36. package/framework/standards/STANDARDS.json +2 -2
  37. package/framework/standards/STANDARDS.meta.json +1 -1
  38. package/framework/standards/ai-agents/setup.md +2 -0
  39. package/framework/standards/backend/dotnet/testing.md +10 -4
  40. package/framework/standards/frontend/nextjs/testing.md +10 -6
  41. package/framework/standards/integration/mcp/mcp-tools.md +3 -1
  42. package/framework/templates/.idea/morph-templates.xml +92 -0
  43. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  44. package/framework/templates/IDE-SNIPPETS.md +262 -262
  45. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  46. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  47. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  48. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  49. package/framework/templates/docs/plan.md +3 -1
  50. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  51. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  52. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  53. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  54. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  55. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  56. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  57. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  58. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  59. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  60. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  61. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  62. package/framework/templates/state.template.json +17 -17
  63. package/framework/templates/ui/design-system.css +226 -226
  64. package/package.json +1 -1
  65. package/src/commands/observability/cost.js +12 -0
  66. package/src/commands/project/status.js +6 -68
  67. package/src/commands/state/advance.js +1 -1
  68. package/src/commands/state/approve.js +2 -2
  69. package/src/commands/state/gate-check.js +2 -2
  70. package/src/commands/state/gate-decision.js +4 -1
  71. package/src/commands/state/next.js +24 -0
  72. package/src/commands/state/report-check.js +3 -1
  73. package/src/commands/validation/mutate.js +9 -6
  74. package/src/commands/validation/plan-check.js +4 -2
  75. package/src/commands/validation/tests-audit.js +7 -0
  76. package/src/commands/validation/validate-feature.js +4 -2
  77. package/src/commands/validation/verify.js +21 -16
  78. package/src/core/state/state-manager.js +4 -0
  79. package/src/dashboard/app/main.jsx +13 -13
  80. package/src/dashboard/app/pages/FeatureDetail.jsx +6 -2
  81. package/src/dashboard/scanner/features-scanner.js +5 -2
  82. package/src/dashboard/vite.config.js +24 -24
  83. package/src/lib/ci/workflow-scaffold.js +12 -0
  84. package/src/lib/e2e/applicability.js +37 -0
  85. package/src/lib/generators/handoff-generator.js +11 -25
  86. package/src/lib/mutation/adapters.js +6 -17
  87. package/src/lib/mutation/runner.js +23 -75
  88. package/src/lib/mutation/trx.js +0 -1
  89. package/src/lib/next-step.js +10 -0
  90. package/src/lib/plan/checks.js +29 -2
  91. package/src/lib/plan/plan-parser.js +6 -2
  92. package/src/lib/review-reports.js +6 -3
  93. package/src/lib/risk-detector.js +20 -7
  94. package/src/lib/stack/stack-scan.js +13 -1
  95. package/src/lib/telemetry/metrics.js +46 -0
  96. package/src/lib/tests-audit/audit.js +96 -0
  97. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  98. package/src/lib/validators/validation-runner.js +6 -38
  99. package/src/lib/validators/verify-runner.js +37 -22
  100. package/src/utils/agents-installer.js +24 -1
  101. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  102. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  103. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  104. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
@@ -9,7 +9,7 @@ import { isTestFile } from '../reuse/symbol-scanner.js';
9
9
  import { isGenerated, isProjectSource } from '../../../../framework/hooks/shared/source-files.js';
10
10
  import { citesProcess, commentLines } from '../../../../framework/hooks/shared/comment-scanner.js';
11
11
 
12
- export const MAX_TEST_RATIO = 1.5;
12
+ export const MAX_TEST_RATIO = 1;
13
13
 
14
14
  /** Abaixo disto a razรฃo nรฃo รฉ julgada: a regressรฃo de um bug de 2 linhas cabe aqui. */
15
15
  export const MIN_TEST_LINES = 40;
@@ -3,11 +3,6 @@ import chalk from 'chalk';
3
3
  import { getAbsoluteOutputPath, resolveContractsOutputType } from '../../core/paths/output-schema.js';
4
4
  import { getStackProfile } from '../stack/stack-profile.js';
5
5
 
6
- // Skipped in 'light' mode (checkpoint-only); light keeps `nextjs-component`.
7
- const HEAVY_VALIDATORS = new Set([
8
- 'packages', 'contract-compliance', 'reuse', 'code-budget'
9
- ]);
10
-
11
6
  /**
12
7
  * @param {string[]} [options.validators] - overrides stack-based detection
13
8
  * @returns {Object} { passed, errors, warnings, issues, summary, results, skipped, _skipReason? }
@@ -24,36 +19,11 @@ export async function runValidation(projectPath, featureName, options = {}) {
24
19
 
25
20
  // A copy: filtering in place would empty the caller's own `options.validators`.
26
21
  const detected = options.validators || detectValidators(projectPath);
27
- let validatorIds = [...detected];
28
-
29
- const mode = options.mode || 'full';
30
- const droppedByMode = [];
31
- if (mode === 'light') {
32
- validatorIds = validatorIds.filter(id => {
33
- if (HEAVY_VALIDATORS.has(id)) { droppedByMode.push(id); return false; }
34
- return true;
35
- });
36
- }
37
-
38
- // Stack-agnostic validators join here, before the "no validators" early return:
39
- // on an `empty`/`nodejs-cli` stack they are the only check. Not in `droppedByMode`,
40
- // which only lists what light mode removed from the stack's own list.
41
- if (!options.skipReuse && mode !== 'light') {
42
- validatorIds.push('reuse');
43
- }
44
- if (mode !== 'light') {
45
- validatorIds.push('code-budget');
46
- }
22
+ const validatorIds = [...detected];
47
23
 
48
- if (validatorIds.length === 0) {
49
- result.summary = droppedByMode.length > 0
50
- ? `No validators ran โ€” light mode skipped ${droppedByMode.length} heavy validator(s): ${droppedByMode.join(', ')}`
51
- : 'No validators ran โ€” no validators configured for this stack';
52
- console.log(chalk.gray(`\n โ„น๏ธ ${result.summary.replace('No validators ran โ€” ', '')}`));
53
- result._skipReason = result.summary;
54
- result._droppedByMode = droppedByMode;
55
- return result;
56
- }
24
+ // Stack-agnostic: on an `empty`/`nodejs-cli` stack they are the only check.
25
+ if (!options.skipReuse) validatorIds.push('reuse');
26
+ validatorIds.push('code-budget');
57
27
 
58
28
  console.log(chalk.gray(`\n Running ${validatorIds.length} validator(s): ${validatorIds.join(', ')}`));
59
29
 
@@ -61,10 +31,8 @@ export async function runValidation(projectPath, featureName, options = {}) {
61
31
  const _profileForContracts = getStackProfile(projectPath);
62
32
  const contractsType = resolveContractsOutputType(_profileForContracts.stack);
63
33
  const contractsPath = getAbsoluteOutputPath(projectPath, featureName, contractsType);
64
- // Positive `isDotnet`, not `!isNextjs`: a "dotnet-nextjs" stack is both. This push
65
- // sits after the mode filter, so light mode has to be honored again here.
66
- const contractsIsHeavyAndSkipped = mode === 'light' && HEAVY_VALIDATORS.has('contract-compliance');
67
- if (existsSync(contractsPath) && _profileForContracts.isDotnet && !contractsIsHeavyAndSkipped) {
34
+ // Positive `isDotnet`, not `!isNextjs`: a "dotnet-nextjs" stack is both.
35
+ if (existsSync(contractsPath) && _profileForContracts.isDotnet) {
68
36
  validatorIds.push('contract-compliance');
69
37
  }
70
38
  }
@@ -5,6 +5,7 @@ import { join, resolve } from 'path';
5
5
  import { runBuildCheck, getBuildSteps, isDotnetStack } from '../../../framework/hooks/shared/build-check.js';
6
6
  import { composeProjectName, runE2eCheck } from '../../../framework/hooks/shared/e2e-check.js';
7
7
  import { buildComposeEnv } from '../e2e/compose-env.js';
8
+ import { e2eApplicability } from '../e2e/applicability.js';
8
9
  import {
9
10
  runTestSuite, parseTestCount, compareTestCount, resolveTestTimeout, discoverDotnetSolution,
10
11
  } from '../tasks/test-runner.js';
@@ -21,7 +22,7 @@ import { runValidation } from './validation-runner.js';
21
22
  import { checkOutputsTraceability } from './traceability-check.js';
22
23
  import { createHeavyGate } from '../heavy/gate.js';
23
24
 
24
- // A dotnet cold build is slow; the Stop-hook default of 30s is too short.
25
+ // A cold dotnet build takes minutes, not seconds.
25
26
  const DEFAULT_BUILD_TIMEOUT = 120_000;
26
27
 
27
28
  const VALIDATOR_FEEDBACK_LIMIT = 10;
@@ -67,6 +68,7 @@ async function runVerifyGated(projectPath, featureName, options, gate) {
67
68
  _runTestSuite = runTestSuite,
68
69
  _runValidation = runValidation,
69
70
  _runE2eCheck = runE2eCheck,
71
+ _e2eApplicability = e2eApplicability,
70
72
  _checkOutputsTraceability = checkOutputsTraceability,
71
73
  } = options;
72
74
 
@@ -150,8 +152,11 @@ async function runVerifyGated(projectPath, featureName, options, gate) {
150
152
  skipE2e,
151
153
  freshE2e,
152
154
  buildNode: nodes.build,
155
+ testsNode: nodes.tests,
156
+ hasStackNodes: stackNodes.length > 0,
153
157
  gate,
154
158
  _runE2eCheck,
159
+ _e2eApplicability,
155
160
  });
156
161
  nodes.e2e = e2e.node;
157
162
  appendE2eFeedback(feedback, nodes.e2e);
@@ -168,12 +173,17 @@ async function runVerifyGated(projectPath, featureName, options, gate) {
168
173
  // warning never turns red.
169
174
  const traceability = _checkOutputsTraceability(projectPath, featureName);
170
175
 
171
- const status = Object.values(nodes).some((n) => n.status === 'fail') ? 'fail' : 'pass';
172
-
173
176
  const baseline = recordBaseline ? recordBaselineFrom(projectPath, nodes.tests) : null;
174
177
  if (baseline && !baseline.recorded) {
175
178
  feedback.push(`[baseline] nรฃo gravada: ${baseline.reason}`);
176
179
  }
180
+ const lowered = baseline?.recorded ? baseline.updates.filter((u) => u.from !== null && u.to < u.from) : [];
181
+ if (lowered.length > 0) {
182
+ feedback.push(`[baseline] rebaixada: ${lowered.map((u) => `${u.id} (${u.from} โ†’ ${u.to})`).join(', ')}`);
183
+ nodes.tests = acceptRecordedDrops(nodes.tests);
184
+ }
185
+
186
+ const status = Object.values(nodes).some((n) => n.status === 'fail') ? 'fail' : 'pass';
177
187
 
178
188
  return {
179
189
  version: 1,
@@ -401,7 +411,7 @@ function judgeEvalRun(entry, node, summaryPath) {
401
411
  }
402
412
 
403
413
  if (entry.status === 'pass' && verdict.verdict === 'drop') {
404
- return { ...withNames, status: 'fail', count, warnings, reason: verdict.reason };
414
+ return { ...withNames, status: 'fail', count, warnings, reason: verdict.reason, countDrop: true };
405
415
  }
406
416
  if (verdict.verdict === 'drop') warnings.push(verdict.reason);
407
417
 
@@ -481,7 +491,7 @@ function judgeRun({ node, result, durationMs }) {
481
491
 
482
492
  // Only a drop fails a node; a rise never does, or failing on growth teaches deleting tests.
483
493
  if (verdict.verdict === 'drop' && result.passed) {
484
- return { ...entry, status: 'fail', durationMs, count, warnings, reason: verdict.reason, output: '' };
494
+ return { ...entry, status: 'fail', durationMs, count, warnings, reason: verdict.reason, output: '', countDrop: true };
485
495
  }
486
496
  if (verdict.verdict === 'drop') warnings.push(verdict.reason);
487
497
 
@@ -497,6 +507,11 @@ function judgeRun({ node, result, durationMs }) {
497
507
  };
498
508
  }
499
509
 
510
+ function acceptRecordedDrops(testsNode) {
511
+ const runs = testsNode.runs.map(({ countDrop, ...run }) => (countDrop ? { ...run, status: 'pass', reason: null } : run));
512
+ return rollUpTestRuns(runs, { command: testsNode.command, source: testsNode.source, environment: testsNode.environment });
513
+ }
514
+
500
515
  // Any red run reddens the rollup; one green run among skips is a pass.
501
516
  function rollUpTestRuns(runs, { command, source, environment = null }) {
502
517
  const failed = runs.filter((r) => r.status === 'fail');
@@ -580,14 +595,15 @@ function baselineTargetProblem(projectPath, testPlan, { skipTests }) {
580
595
  }
581
596
 
582
597
  // Only a run with EVERY node green records: a red run would freeze the regression,
583
- // a partial one would claim counts for nodes nobody measured.
598
+ // a partial one would claim counts for nodes nobody measured. A count drop on a run that
599
+ // exited 0 is the one red it accepts: lowering the number is how deleting tests passes.
584
600
  function recordBaselineFrom(projectPath, testsNode) {
585
601
  const runs = testsNode.runs || [];
586
602
  const refuse = (reason) => ({ recorded: false, reason, updates: [] });
587
603
 
588
604
  if (runs.length === 0) return refuse('nenhum nรณ de teste executou');
589
605
 
590
- const notGreen = runs.filter((r) => r.status !== 'pass');
606
+ const notGreen = runs.filter((r) => r.status !== 'pass' && !r.countDrop);
591
607
  if (notGreen.length > 0) {
592
608
  const named = notGreen.map((r) => `${r.id} (${r.status}${r.reason ? `: ${r.reason}` : ''})`).join('; ');
593
609
  return refuse(
@@ -609,7 +625,7 @@ async function runValidatorsNode({ projectPath, featureName, _runValidation }) {
609
625
  try {
610
626
  console.log = () => {};
611
627
  console.warn = () => {};
612
- validationResult = await _runValidation(projectPath, featureName, { mode: 'full' });
628
+ validationResult = await _runValidation(projectPath, featureName);
613
629
  } finally {
614
630
  console.log = originalLog;
615
631
  console.warn = originalWarn;
@@ -658,21 +674,20 @@ function appendValidatorsFeedback(feedback, validatorsNode) {
658
674
  }
659
675
  }
660
676
 
661
- async function runE2eNode({ projectPath, featureName, skipE2e, freshE2e, buildNode, gate, _runE2eCheck }) {
662
- if (buildNode.status === 'fail') {
663
- return {
664
- node: {
665
- status: 'skip',
666
- command: null,
667
- durationMs: 0,
668
- reason: 'build failed โ€” the stack could not come up anyway',
669
- output: '',
670
- },
671
- stackEnv: null,
672
- };
673
- }
677
+ const skippedE2e = (reason) => ({ node: { status: 'skip', command: null, durationMs: 0, reason, output: '' }, stackEnv: null });
678
+
679
+ async function runE2eNode({
680
+ projectPath, featureName, skipE2e, freshE2e, buildNode, testsNode, hasStackNodes, gate, _runE2eCheck, _e2eApplicability,
681
+ }) {
682
+ if (buildNode.status === 'fail') return skippedE2e('build failed โ€” the stack could not come up anyway');
683
+ if (testsNode?.status === 'fail') return skippedE2e('tests failed โ€” the e2e does not run over a red suite');
674
684
 
675
685
  const config = readConfig(projectPath);
686
+ // An `e2e: true` test node is the backend's proof against the stack: it always brings the stack up.
687
+ if (!skipE2e && !hasStackNodes) {
688
+ const applicability = _e2eApplicability(projectPath, featureName, config);
689
+ if (!applicability.applicable) return skippedE2e(applicability.reason);
690
+ }
676
691
  const gitCommonDir = resolveGitCommonDir(projectPath);
677
692
  // Without the port block, two worktrees verifying at once collide on host ports.
678
693
  const env = buildComposeEnv({ cwd: projectPath, config, featureName, gitCommonDir });
@@ -698,7 +713,7 @@ async function runE2eNode({ projectPath, featureName, skipE2e, freshE2e, buildNo
698
713
  command: result.command ?? null,
699
714
  durationMs,
700
715
  reason: result.reason ?? null,
701
- output: result.output || '',
716
+ output: tailLines(result.output || '', OUTPUT_TAIL_LINES),
702
717
  count: Number.isInteger(result.count) ? result.count : null,
703
718
  warnings: Array.isArray(result.warnings) ? result.warnings : [],
704
719
  },
@@ -2,6 +2,7 @@ import { readFileSync, existsSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'path';
3
3
  import { shouldInstall } from '../lib/stack-filter.js';
4
4
  import { realEffects } from '../lib/installers/effects.js';
5
+ import { REVIEW_ROLES, VERDICT_TOKEN, REVIEWED_SHA_TOKEN, VERDICTS } from '../lib/review-reports.js';
5
6
 
6
7
  export const DEFAULT_AGENT_TOOLS = 'Read, Write, Edit, Bash, Glob, Grep';
7
8
 
@@ -283,7 +284,7 @@ export const DISPATCH_CONTRACT = [
283
284
  'ANTES de encerrar โ€” รฉ o retorno autoritativo, nรฃo a sua mensagem final. `SendMessage` nรฃo รฉ substituto do relatรณrio e nรฃo deve ser chamado.',
284
285
  '2. ESTADO: NรƒO toque em `plan.md`, `checks.json`, `feature.json`, `state.json` nem no board de sessรฃo โ€” todo estado รฉ gravado exclusivamente pelo orquestrador.',
285
286
  '3. TESTE: a prova รฉ do orquestrador (`morph-spec verify {feature}`, depois do seu retorno), nรฃo sua. Escreva os testes pelo orรงamento: ' +
286
- 'um por pรณs-condiรงรฃo, um de regressรฃo por bug, variantes numa tabela (`[Theory]`/`it.each`). NรƒO rode teste a cada marco nem faรงa ciclo redโ†’green: ' +
287
+ 'um por Cn `prova: teste`, variantes em tabela (`[Theory]`/`it.each`); `leitura` sem teste. NรƒO rode teste a cada marco nem faรงa ciclo redโ†’green: ' +
287
288
  'escreva cรณdigo e testes, compile, e rode Sร“ o filtro dos seus testes UMA vez, em primeiro plano, antes do relatรณrio; se falhar, corrija e rode sรณ os que falharam. ' +
288
289
  'Grave a saรญda em arquivo (`โ€ฆ > .morph/logs/test-last.log 2>&1`, sem nada depois) e leia o arquivo โ€” um pipe troca o exit do teste pelo do รบltimo comando. ' +
289
290
  'Em batch paralelo SEM isolamento, sรณ compile. Com `isolation: "worktree"`, o mesmo vale no seu worktree, e o รบltimo ato รฉ o commit na branch dele.',
@@ -300,6 +301,8 @@ export const DISPATCH_CONTRACT = [
300
301
  '`git checkout --`). A prova de que o teste morde รฉ do verificador (Gate 3).',
301
302
  '9. ESCRITA: cรณdigo e teste do projeto sรณ por Write/Edit, nunca pelo Bash โ€” heredoc, redirecionamento, `sed -i` ou script python/node que grava (`cat > X.cs <<EOF`, `sed -i โ€ฆ X.cs`): ' +
302
303
  'os guards de escrita sรณ enxergam Write/Edit.',
304
+ '10. BLOQUEIO: decisรฃo que nรฃo รฉ sua (requisito contra o plano ou um ADR, escolha deixada ao humano) โ†’ `BLOQUEADO` na 1ยช linha, ' +
305
+ 'com a pergunta e as opรงรตes. Dificuldade tรฉcnica nรฃo รฉ bloqueio.',
303
306
  '',
304
307
  'Formato do relatรณrio (o `report-check` lรช estas marcas):',
305
308
  '',
@@ -317,6 +320,25 @@ export const DISPATCH_CONTRACT = [
317
320
  'Problemas e como foram resolvidos vรฃo em prosa depois.',
318
321
  ];
319
322
 
323
+ export const REVIEW_REPORT_CONTRACT = [
324
+ '## Relatรณrio do Gate 3',
325
+ '',
326
+ 'Grave o relatรณrio que o dispatch nomeia ANTES de encerrar โ€” o arquivo รฉ o retorno, nรฃo a mensagem final. ' +
327
+ 'Se ele jรก existir quando vocรช comeรงar, รฉ resรญduo de outra rodada: nรฃo o leia antes de escrever o seu (ancoragem no veredito alheio), ' +
328
+ 'e nรฃo leia os relatรณrios dos outros papรฉis.',
329
+ '',
330
+ `Primeira linha \`${VERDICT_TOKEN} ${VERDICTS.join('|')}\`, depois \`${REVIEWED_SHA_TOKEN} {sha}\`, ` +
331
+ 'tabela `## Bloqueadores` (`| id | tipo | onde | evidรชncia |`; vazia = `Nenhum.`) e `## Notas`. ' +
332
+ 'Bloqueador sรณ com evidรชncia e tipo; o resto รฉ nota, e nota nunca pede rodada nova.',
333
+ '',
334
+ 'Numa confirmaรงรฃo (o orquestrador te retoma depois da correรงรฃo, com o `sha` novo) acrescente ao fim uma seรงรฃo nova com ' +
335
+ `\`${VERDICT_TOKEN}\` e \`${REVIEWED_SHA_TOKEN} {sha novo}\`: se vocรช abriu bloqueadores, julgue sรณ eles; ` +
336
+ 'se nรฃo abriu, julgue sรณ o delta `{sha anterior}..{sha novo}` โ€” o que a correรงรฃo abriu. ' +
337
+ 'O relatรณrio รฉ append-only: vale o รบltimo veredito e, na `## Checks`, a รบltima linha de cada Cn.',
338
+ '',
339
+ 'Um comando por chamada de Bash, sem encadear (`;`, `&&`, pipe): o composto รฉ negado e custa a rodada.',
340
+ ];
341
+
320
342
  function buildBody(agent, maxTurns) {
321
343
  const lines = [
322
344
  `# ${agent.title ?? agent.id}`,
@@ -374,6 +396,7 @@ function buildBody(agent, maxTurns) {
374
396
  'O entregรกvel รฉ o arquivo de relatรณrio que o dispatch nomeia; sem arquivo nomeado, รฉ a sua mensagem final (no scout, o mapa).',
375
397
  '',
376
398
  );
399
+ if (Object.hasOwn(REVIEW_ROLES, agent.id)) lines.push(...REVIEW_REPORT_CONTRACT, '');
377
400
  }
378
401
 
379
402
  lines.push(
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Scope: universal | Validates commit message format (Conventional Commits)
3
- # MORPH-SPEC Commit Message Hook
4
- # Enforces Conventional Commits specification
5
-
6
- COMMIT_MSG_FILE="$1"
7
- COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
8
-
9
- # Conventional Commits pattern: type(scope): description
10
- # Types: feat, fix, docs, style, refactor, perf, test, chore, build, ci, revert
11
- PATTERN="^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\(.+\))?: .{1,100}"
12
-
13
- if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
14
- echo "โŒ Invalid commit message format!"
15
- echo ""
16
- echo "Required format: <type>(<scope>): <description>"
17
- echo ""
18
- echo "Types: feat, fix, docs, style, refactor, perf, test, chore, build, ci, revert"
19
- echo ""
20
- echo "Examples:"
21
- echo " feat(auth): add JWT refresh token rotation"
22
- echo " fix(api): resolve null reference in UserService"
23
- echo " docs(readme): update installation instructions"
24
- echo ""
25
- echo "Your message:"
26
- echo " $COMMIT_MSG"
27
- echo ""
28
- echo "Override with: git commit --no-verify"
29
- exit 1
30
- fi
31
-
32
- echo "โœ… Commit message format valid"
33
- exit 0
@@ -1,25 +0,0 @@
1
- #!/bin/bash
2
- # Scope: universal | Validates agents.json Schema
3
- # MORPH-SPEC Pre-Commit Hook: Agent Configuration Validation
4
- # Uses validate-agents.js to check agents.json
5
-
6
- echo "๐Ÿค– Validating agent configuration..."
7
-
8
- # Check if agents.json is being modified
9
- if git diff --cached --name-only | grep -q 'agents\.json$'; then
10
- echo "Detected changes to agents.json"
11
-
12
- # Run validator
13
- if ! morph-spec validate-agents-skills; then
14
- echo ""
15
- echo "โŒ COMMIT BLOCKED: agents.json validation failed"
16
- echo " Fix errors above before committing"
17
- exit 1
18
- fi
19
-
20
- echo "โœ“ agents.json is valid"
21
- else
22
- echo "โœ“ No changes to agents.json"
23
- fi
24
-
25
- exit 0
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Scope: universal | Orchestrates framework + stack-specific pre-commit hooks
3
- # MORPH-SPEC Master Pre-Commit Hook
4
- # Runs universal framework hooks + stack-specific hooks in sequence
5
-
6
- echo "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—"
7
- echo "โ•‘ MORPH-SPEC PRE-COMMIT VALIDATION โ•‘"
8
- echo "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"
9
- echo ""
10
-
11
- FRAMEWORK_HOOKS_DIR="$(dirname "$0")"
12
- STACK_HOOKS_DIR=".morph/hooks/pre-commit"
13
-
14
- HAS_FAILURES=false
15
-
16
- # Run framework hooks (universal - all stacks)
17
- echo "๐Ÿ“ฆ Running framework hooks (universal)..."
18
- for hook in "$FRAMEWORK_HOOKS_DIR"/*.sh; do
19
- # Skip orchestrator itself
20
- if [[ "$hook" == */orchestrator.sh ]]; then
21
- continue
22
- fi
23
-
24
- if [[ -f "$hook" ]]; then
25
- hook_name=$(basename "$hook")
26
- echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€"
27
- if ! bash "$hook"; then
28
- HAS_FAILURES=true
29
- echo "โŒ Framework hook failed: $hook_name"
30
- fi
31
- echo ""
32
- fi
33
- done
34
-
35
- # Run stack-specific hooks (if exist)
36
- if [[ -d "$STACK_HOOKS_DIR" ]]; then
37
- echo "๐ŸŽฏ Running stack-specific hooks..."
38
- for hook in "$STACK_HOOKS_DIR"/*.sh; do
39
- if [[ -f "$hook" ]]; then
40
- hook_name=$(basename "$hook")
41
- echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€"
42
- if ! bash "$hook"; then
43
- HAS_FAILURES=true
44
- echo "โŒ Stack hook failed: $hook_name"
45
- fi
46
- echo ""
47
- fi
48
- done
49
- else
50
- echo "โ„น๏ธ No stack-specific hooks found (optional)"
51
- echo ""
52
- fi
53
-
54
- echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"
55
-
56
- if [[ "$HAS_FAILURES" == "true" ]]; then
57
- echo "โŒ PRE-COMMIT VALIDATION FAILED"
58
- echo " Fix errors above before committing"
59
- echo " Or use: git commit --no-verify to skip"
60
- exit 1
61
- fi
62
-
63
- echo "โœ… ALL PRE-COMMIT VALIDATIONS PASSED"
64
- exit 0
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Scope: universal | Runs test suite before push
3
- # MORPH-SPEC Pre-Push Hook
4
- # Ensures all tests pass before pushing to remote
5
-
6
- echo "๐Ÿงช Running test suite before push..."
7
- echo ""
8
-
9
- # Detect project type and run appropriate tests
10
- if [[ -f *.csproj ]] || find . -maxdepth 2 -name "*.csproj" 2>/dev/null | grep -q .; then
11
- # .NET project detected
12
- echo "๐Ÿ“ฆ Detected .NET project"
13
-
14
- if ! dotnet test --verbosity minimal --no-build --no-restore 2>/dev/null; then
15
- echo ""
16
- echo "โŒ .NET tests failed!"
17
- echo ""
18
- echo "Fix failing tests before pushing."
19
- echo "Override with: git push --no-verify"
20
- exit 1
21
- fi
22
-
23
- elif [[ -f package.json ]]; then
24
- # Node.js project detected
25
- echo "๐Ÿ“ฆ Detected Node.js project"
26
-
27
- if ! npm test 2>/dev/null; then
28
- echo ""
29
- echo "โŒ Node.js tests failed!"
30
- echo ""
31
- echo "Fix failing tests before pushing."
32
- echo "Override with: git push --no-verify"
33
- exit 1
34
- fi
35
-
36
- else
37
- echo "โš ๏ธ No recognized test framework found"
38
- echo " Skipping test execution"
39
- exit 0
40
- fi
41
-
42
- echo ""
43
- echo "โœ… All tests passed - safe to push"
44
- exit 0