@polymorphism-tech/morph-spec 9.1.0 โ†’ 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +7 -1
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +1 -0
  7. package/framework/MORPH.md +2 -2
  8. package/framework/agents.json +8 -8
  9. package/framework/docs/CLI.md +16 -14
  10. package/framework/evals/review-checklist.md +12 -2
  11. package/framework/hooks/README.md +8 -44
  12. package/framework/hooks/claude-code/core/state-sync.js +1 -1
  13. package/framework/hooks/claude-code/statusline.sh +11 -11
  14. package/framework/rules/nextjs-standards.md +1 -1
  15. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  16. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  17. package/framework/skills/core/morph-apply/SKILL.md +3 -3
  18. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  19. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  20. package/framework/skills/core/morph-media/SKILL.md +1 -1
  21. package/framework/skills/core/morph-plan/SKILL.md +8 -6
  22. package/framework/skills/core/morph-review/SKILL.md +9 -6
  23. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  24. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  25. package/framework/standards/STANDARDS.json +2 -2
  26. package/framework/standards/backend/dotnet/testing.md +10 -4
  27. package/framework/standards/frontend/nextjs/testing.md +10 -6
  28. package/framework/templates/.idea/morph-templates.xml +92 -0
  29. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  30. package/framework/templates/IDE-SNIPPETS.md +262 -262
  31. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  32. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  33. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  34. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  35. package/framework/templates/docs/plan.md +3 -1
  36. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  37. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  38. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  39. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  40. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  41. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  42. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  43. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  44. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  45. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  46. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  47. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  48. package/framework/templates/state.template.json +17 -17
  49. package/framework/templates/ui/design-system.css +226 -226
  50. package/package.json +1 -1
  51. package/src/commands/state/approve.js +2 -2
  52. package/src/commands/validation/mutate.js +9 -6
  53. package/src/commands/validation/plan-check.js +4 -2
  54. package/src/commands/validation/tests-audit.js +7 -0
  55. package/src/commands/validation/validate-feature.js +4 -2
  56. package/src/commands/validation/verify.js +15 -15
  57. package/src/dashboard/app/main.jsx +13 -13
  58. package/src/dashboard/vite.config.js +24 -24
  59. package/src/lib/ci/workflow-scaffold.js +12 -0
  60. package/src/lib/e2e/applicability.js +37 -0
  61. package/src/lib/mutation/adapters.js +6 -17
  62. package/src/lib/mutation/runner.js +23 -75
  63. package/src/lib/mutation/trx.js +0 -1
  64. package/src/lib/plan/checks.js +29 -2
  65. package/src/lib/plan/plan-parser.js +6 -2
  66. package/src/lib/review-reports.js +6 -3
  67. package/src/lib/risk-detector.js +2 -0
  68. package/src/lib/tests-audit/audit.js +96 -0
  69. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  70. package/src/lib/validators/validation-runner.js +6 -38
  71. package/src/lib/validators/verify-runner.js +36 -21
  72. package/src/utils/agents-installer.js +1 -1
  73. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  74. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  75. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  76. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
@@ -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 });
@@ -283,7 +283,7 @@ export const DISPATCH_CONTRACT = [
283
283
  '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
284
  '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
285
  '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: ' +
286
+ '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
287
  '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
288
  '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
289
  '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.',
@@ -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