@polymorphism-tech/morph-spec 8.36.0 → 8.37.1
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.
- package/CLAUDE.md +5 -5
- package/README.md +2 -2
- package/bin/morph-spec.js +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/docs/CLI.md +47 -24
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +1 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +2 -2
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +7 -3
- package/framework/templates/dotnet/ai-kit/README.md +12 -7
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +12 -6
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +16 -11
- package/package.json +1 -1
- package/src/commands/validation/verify.js +21 -8
- package/src/lib/tasks/dotnet-test-introspection.js +134 -19
- package/src/lib/tasks/eval-node.js +3 -3
- package/src/lib/tasks/test-runner.js +88 -15
- package/src/lib/tasks/test-scope.js +47 -17
- package/src/lib/validators/verify-runner.js +46 -15
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { execFileSync } from 'child_process';
|
|
27
|
-
import { existsSync, readFileSync } from 'fs';
|
|
27
|
+
import { existsSync, readFileSync, rmSync } from 'fs';
|
|
28
28
|
import { join, resolve } from 'path';
|
|
29
29
|
|
|
30
30
|
import { runBuildCheck, getBuildSteps } from '../../../framework/hooks/shared/build-check.js';
|
|
31
31
|
import { runE2eCheck } from '../../../framework/hooks/shared/e2e-check.js';
|
|
32
32
|
import { buildComposeEnv } from '../e2e/compose-env.js';
|
|
33
33
|
import {
|
|
34
|
-
runTestSuite, parseTestCount, compareTestCount, resolveTestTimeout, isZeroTestRun,
|
|
34
|
+
runTestSuite, parseTestCount, compareTestCount, resolveTestTimeout, isZeroTestRun, mtpSummaryTotals,
|
|
35
35
|
} from '../tasks/test-runner.js';
|
|
36
36
|
import {
|
|
37
37
|
resolveTestNodes, parseTestFilter, nodeMatchesFilter, recordTestBaselines, morphConfigPath,
|
|
@@ -56,6 +56,7 @@ const OUTPUT_TAIL_LINES = 20;
|
|
|
56
56
|
|
|
57
57
|
/** A narrowed run that proved ZERO tests ran (#87) — wording pinned by the plan. */
|
|
58
58
|
const NARROWED_ZERO_TESTS = 'narrowed-zero-tests: o recorte executou 0 testes';
|
|
59
|
+
const NARROWED_ZERO_TESTS_LINK = 'narrowed-zero-tests: um link do recorte executou 0 testes (bloco MTP com total: 0)';
|
|
59
60
|
|
|
60
61
|
/** A narrowed run that exited green with no count anyone could read (#87): null is UNKNOWN, and unknown is never green. */
|
|
61
62
|
const NARROWED_COUNT_UNKNOWN = 'narrowed-count-unknown: o recorte saiu verde sem contagem legível — '
|
|
@@ -469,14 +470,27 @@ async function runOneTestNode({
|
|
|
469
470
|
const evalChannel = node.runner === EVAL_RUNNER
|
|
470
471
|
? evalNodeEnv(node, projectPath, { refresh: refreshCache })
|
|
471
472
|
: null;
|
|
473
|
+
// The summary path is fixed per node id and pid, and nothing else removes the
|
|
474
|
+
// file: one left there by an earlier run (an earlier verify in the same
|
|
475
|
+
// process, a recycled pid) was read as THIS run's, handing a harness that
|
|
476
|
+
// wrote nothing the old scenario count — and hiding an empty run behind it.
|
|
477
|
+
if (evalChannel) discardFile(evalChannel.summaryPath);
|
|
472
478
|
|
|
473
479
|
const start = Date.now();
|
|
474
480
|
const result = await _runTestSuite(projectPath, {
|
|
475
481
|
timeout, node, ...(evalChannel ? { env: evalChannel.env } : {}),
|
|
476
482
|
});
|
|
477
483
|
const entry = judgeRun({ node, result, durationMs: Date.now() - start });
|
|
484
|
+
if (!evalChannel) return entry;
|
|
478
485
|
|
|
479
|
-
|
|
486
|
+
const judged = judgeEvalRun(entry, node, evalChannel.summaryPath);
|
|
487
|
+
discardFile(evalChannel.summaryPath);
|
|
488
|
+
return judged;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** Remove a scratch file the CLI owns; a file that is not there (or will not go) changes nothing. */
|
|
492
|
+
function discardFile(path) {
|
|
493
|
+
try { rmSync(path, { force: true }); } catch { /* best effort — the next run removes it first */ }
|
|
480
494
|
}
|
|
481
495
|
|
|
482
496
|
/**
|
|
@@ -484,10 +498,12 @@ async function runOneTestNode({
|
|
|
484
498
|
* code said.
|
|
485
499
|
*
|
|
486
500
|
* Two things the exit code cannot see:
|
|
487
|
-
* - the scenario COUNT, which is what `expectedCount` is compared against
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
501
|
+
* - the scenario COUNT, which is what `expectedCount` is compared against and
|
|
502
|
+
* what `--record-baseline` writes. Read ONLY from the summary: the `dotnet`
|
|
503
|
+
* extractor counts xunit cases, another unit — compared with (or recorded as)
|
|
504
|
+
* a scenario baseline it failed green runs as drops (#110, #117 review). No
|
|
505
|
+
* readable summary → **null, UNKNOWN, never 0** — an unreadable summary must
|
|
506
|
+
* not manufacture a count drop;
|
|
491
507
|
* - a suite that evaluated ZERO scenarios and exited 0. Nothing failed, because
|
|
492
508
|
* nothing ran: the guardrail-inert shape this repository has already paid to
|
|
493
509
|
* learn to detect.
|
|
@@ -499,8 +515,7 @@ function judgeEvalRun(entry, node, summaryPath) {
|
|
|
499
515
|
const cold = evalColdCacheWarning(summary);
|
|
500
516
|
if (cold) warnings.push(cold);
|
|
501
517
|
|
|
502
|
-
const
|
|
503
|
-
const count = fromSummary ?? entry.count;
|
|
518
|
+
const count = evalScenarioCount(summary);
|
|
504
519
|
const verdict = compareTestCount(count, node.expectedCount);
|
|
505
520
|
if (verdict.warning) warnings.push(verdict.warning);
|
|
506
521
|
|
|
@@ -645,7 +660,12 @@ function judgeRun({ node, result, durationMs }) {
|
|
|
645
660
|
return judgeNarrowedRun({ entry, node, result, durationMs, count });
|
|
646
661
|
}
|
|
647
662
|
|
|
648
|
-
|
|
663
|
+
// An eval node's unit is the SCENARIO (#110 part 2): its baseline is the
|
|
664
|
+
// summary's scenario count, and `judgeEvalRun` is the one that compares it.
|
|
665
|
+
// Comparing here too, with the extractor's count of xunit cases, failed a
|
|
666
|
+
// 24/24 run as "esperado 24, executado 2" — and `judgeEvalRun` only turns a
|
|
667
|
+
// pass into a fail, never the reverse.
|
|
668
|
+
const verdict = compareTestCount(count, node.runner === EVAL_RUNNER ? null : node.expectedCount);
|
|
649
669
|
const warnings = verdict.warning ? [verdict.warning] : [];
|
|
650
670
|
|
|
651
671
|
// A drop is the one count verdict that fails a node by itself: tests that
|
|
@@ -689,16 +709,24 @@ function judgeRun({ node, result, durationMs }) {
|
|
|
689
709
|
* must never buy. What counts as proof is the selection's `zeroGuard`
|
|
690
710
|
* (test-scope.js):
|
|
691
711
|
* `count` only a parsed count > 0. **null is UNKNOWN, never green**;
|
|
692
|
-
* `mtp-exit-8`
|
|
693
|
-
*
|
|
712
|
+
* `mtp-exit-8` a parsed count > 0 proves it (the MTP summary is read in
|
|
713
|
+
* pt-BR and en-US, #115); with no readable summary (another
|
|
714
|
+
* locale), exit 0 does — MTP exits 8 on zero tests.
|
|
715
|
+
* A parsed 0 is refused under either guard: it is positive evidence. It is
|
|
716
|
+
* also what closes limit (f): with exit 8 IGNORED, MTP exits 0 over a
|
|
717
|
+
* summary that says `total: 0` (measured, mtp-ignore-exit-code-8-zero-tests.txt).
|
|
718
|
+
* In a narrowed `&&` chain the SUM would hide that link behind another's
|
|
719
|
+
* count, so under `mtp-exit-8` every MTP block is checked on its own — MTP
|
|
720
|
+
* prints one block per invocation (measured), and a `total: 0` block is red.
|
|
694
721
|
*
|
|
695
722
|
* A FAILED run is renamed `narrowed-zero-tests (…)` only when it carries the
|
|
696
723
|
* runner's zero-test signature AND no count proves tests ran. A multi-project
|
|
697
724
|
* VSTest output may print "No test matches…" for one project while another
|
|
698
725
|
* ran and failed; that failure is the lead, never a zero. Anything else,
|
|
699
|
-
* exit 9 included, keeps the executor's own raw reason:
|
|
700
|
-
* `--minimum-expected-tests`
|
|
701
|
-
*
|
|
726
|
+
* exit 9 included, keeps the executor's own raw reason: a project that imposes
|
|
727
|
+
* `--minimum-expected-tests` is narrowed only with its floor lowered to 1 by
|
|
728
|
+
* `-p:` (#114, test-scope.js), where a run with tests cannot exit 9 — so a 9
|
|
729
|
+
* here is not ours to reinterpret.
|
|
702
730
|
*
|
|
703
731
|
* Every red leads its output with the verdict, like a timeout does: the
|
|
704
732
|
* feedback line reads `output` before `reason`, and a tail alone would bury it.
|
|
@@ -712,6 +740,9 @@ function judgeNarrowedRun({ entry, node, result, durationMs, count }) {
|
|
|
712
740
|
|
|
713
741
|
if (result.passed) {
|
|
714
742
|
if (count === 0) return red(NARROWED_ZERO_TESTS);
|
|
743
|
+
if (entry.selection.zeroGuard === 'mtp-exit-8' && mtpSummaryTotals(result.output).includes(0)) {
|
|
744
|
+
return red(NARROWED_ZERO_TESTS_LINK);
|
|
745
|
+
}
|
|
715
746
|
if (ranTests) return green();
|
|
716
747
|
if (entry.selection.zeroGuard === 'mtp-exit-8' && result.exitCode === 0) return green();
|
|
717
748
|
return red(NARROWED_COUNT_UNKNOWN);
|