forge-workflow 0.1.0-beta.6 → 0.1.0-beta.8
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/CHANGELOG.md +36 -0
- package/bin/forge-gh-proxy.js +15 -0
- package/bin/forge-github-credential.js +21 -0
- package/bin/forge.js +16 -1
- package/docs/guides/MIGRATION.md +4 -3
- package/docs/reference/RELEASE.md +5 -3
- package/docs/reference/VALIDATION.md +14 -0
- package/docs/reference/github-accounts.md +114 -38
- package/lib/bun-workflow-pins.js +129 -3
- package/lib/commands/github.js +234 -49
- package/lib/commands/merge.js +2 -2
- package/lib/commands/push.js +308 -47
- package/lib/commands/release.js +18 -1
- package/lib/commands/shepherd.js +1 -1
- package/lib/commands/test.js +8 -0
- package/lib/commands/validate.js +219 -67
- package/lib/gh-proxy.js +309 -0
- package/lib/github-context.js +160 -25
- package/lib/github-credential.js +47 -0
- package/lib/github-router.js +774 -0
- package/lib/kernel/migrations.js +1 -1
- package/lib/kernel/sqlite-driver.js +1 -1
- package/lib/memory/router.js +1 -1
- package/lib/memory/usage-evidence.js +1 -1
- package/lib/native-gh.js +177 -0
- package/lib/npm-publish-workflow.js +5 -5
- package/lib/pr-monitor/flow-monitor.js +127 -28
- package/lib/pr-monitor/reconcile-executor.js +1 -1
- package/lib/project-memory.js +1 -1
- package/lib/protected-state-authority.js +214 -13
- package/lib/release-readiness.js +57 -41
- package/lib/shell-utils.js +41 -15
- package/lib/test-workflow.js +389 -0
- package/lib/validation-receipt.js +305 -0
- package/lib/workflow-templates/test.yml +654 -0
- package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/node_modules/@forge/contracts/index.js +32 -0
- package/node_modules/@forge/contracts/package.json +35 -0
- package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/node_modules/@forge/contracts/src/identity.js +42 -0
- package/node_modules/@forge/contracts/src/schema.js +72 -0
- package/node_modules/@forge/contracts/src/validate.js +305 -0
- package/node_modules/@forge/flow/index.js +114 -0
- package/node_modules/@forge/flow/package.json +34 -0
- package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
- package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
- package/node_modules/@forge/flow/src/executor.js +279 -0
- package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
- package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
- package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
- package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
- package/node_modules/@forge/memory/index.js +331 -0
- package/node_modules/@forge/memory/package.json +34 -0
- package/node_modules/@forge/memory/src/authority-provider.js +67 -0
- package/node_modules/@forge/memory/src/backend-registry.js +166 -0
- package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
- package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
- package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
- package/package.json +12 -2
- package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
- package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
- package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
- package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
- package/scripts/check-forge-token.js +85 -67
- package/scripts/gen-embedded-assets.mjs +31 -5
- package/scripts/test-full-suite.js +69 -15
- package/scripts/test.js +11 -1
package/lib/commands/validate.js
CHANGED
|
@@ -12,6 +12,7 @@ const { execFileSync } = require('node:child_process');
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
13
|
const path = require('node:path');
|
|
14
14
|
const { secureExecFileSync } = require('../shell-utils');
|
|
15
|
+
const validationReceipt = require('../validation-receipt');
|
|
15
16
|
|
|
16
17
|
// Constants
|
|
17
18
|
const CHECK_TYPES = {
|
|
@@ -94,7 +95,6 @@ function getExecOptions(rootDir = process.cwd(), fullSuite = false) {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
const ERROR_PATTERNS = {
|
|
97
|
-
COMMAND_NOT_FOUND: ['ENOENT', 'not found'],
|
|
98
98
|
NO_LOCK_FILE: ['requires an existing', 'package-lock'],
|
|
99
99
|
GIT_LISTING_UNAVAILABLE: [
|
|
100
100
|
'not a git repository',
|
|
@@ -112,9 +112,7 @@ const ERROR_PATTERNS = {
|
|
|
112
112
|
* @private
|
|
113
113
|
*/
|
|
114
114
|
function isCommandNotFound(error) {
|
|
115
|
-
return
|
|
116
|
-
error.message.includes(pattern),
|
|
117
|
-
);
|
|
115
|
+
return error?.code === 'ENOENT';
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
function isGitListingUnavailableError(error) {
|
|
@@ -299,6 +297,7 @@ async function runTypeCheck() {
|
|
|
299
297
|
return {
|
|
300
298
|
success: true,
|
|
301
299
|
skipped: true,
|
|
300
|
+
notConfigured: true,
|
|
302
301
|
duration: Date.now() - startTime,
|
|
303
302
|
message: 'TypeScript not configured (no tsconfig.json)',
|
|
304
303
|
};
|
|
@@ -358,7 +357,7 @@ async function runTypeCheck() {
|
|
|
358
357
|
* const result = await runLint();
|
|
359
358
|
* console.log(`Warnings: ${result.warnings}, Errors: ${result.errors}`);
|
|
360
359
|
*/
|
|
361
|
-
async function runLint(exec =
|
|
360
|
+
async function runLint(exec = secureExecFileSync) {
|
|
362
361
|
const startTime = Date.now();
|
|
363
362
|
|
|
364
363
|
try {
|
|
@@ -575,6 +574,21 @@ async function runSecurityScan() { // NOSONAR S3776
|
|
|
575
574
|
*/
|
|
576
575
|
function parseTestCounts(output) {
|
|
577
576
|
const text = String(output || '');
|
|
577
|
+
const effectiveBudgetMatch = /Full suite resource budget: requested=(default|\d+) effective=(\d+)/.exec(text);
|
|
578
|
+
const rejectedBudgetMatch = /Full suite resource budget: requested=(\d+) minimum=(\d+) outcome=rejected/.exec(text);
|
|
579
|
+
let resourceBudget;
|
|
580
|
+
if (effectiveBudgetMatch) {
|
|
581
|
+
resourceBudget = {
|
|
582
|
+
requested: effectiveBudgetMatch[1] === 'default' ? null : Number(effectiveBudgetMatch[1]),
|
|
583
|
+
effective: Number(effectiveBudgetMatch[2]),
|
|
584
|
+
};
|
|
585
|
+
} else if (rejectedBudgetMatch) {
|
|
586
|
+
resourceBudget = {
|
|
587
|
+
requested: Number(rejectedBudgetMatch[1]),
|
|
588
|
+
minimum: Number(rejectedBudgetMatch[2]),
|
|
589
|
+
outcome: 'rejected',
|
|
590
|
+
};
|
|
591
|
+
}
|
|
578
592
|
const aggregateMatches = [...text.matchAll(
|
|
579
593
|
/Full suite aggregate: status=(PASS|FAIL|INCOMPLETE) tests=(\d+) assertions=\d+ passed=(\d+) failed=(\d+) errors=(\d+) skipped=(\d+)/g,
|
|
580
594
|
)]; // NOSONAR S5852 - fixed tokens and bounded numeric fields
|
|
@@ -583,6 +597,7 @@ function parseTestCounts(output) {
|
|
|
583
597
|
const total = parseNumber(aggregate, 2);
|
|
584
598
|
return {
|
|
585
599
|
status: aggregate[1],
|
|
600
|
+
resourceBudget,
|
|
586
601
|
passed: parseNumber(aggregate, 3),
|
|
587
602
|
failed: parseNumber(aggregate, 4),
|
|
588
603
|
errors: parseNumber(aggregate, 5),
|
|
@@ -596,7 +611,24 @@ function parseTestCounts(output) {
|
|
|
596
611
|
const skipped = parseNumber(/(\d+) skip/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
|
|
597
612
|
const ranMatch = /Ran (\d+) tests/.exec(text); // NOSONAR S5852 - bounded \d+ pattern
|
|
598
613
|
const total = ranMatch ? parseNumber(ranMatch, 1) : passed + failed + skipped;
|
|
599
|
-
return { passed, failed, skipped, total, testsFound: total > 0 };
|
|
614
|
+
return { passed, failed, skipped, total, testsFound: total > 0, resourceBudget };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function parseResourceBudget(args = []) {
|
|
618
|
+
let budget = null;
|
|
619
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
620
|
+
if (args[index] !== '--shards') continue;
|
|
621
|
+
const value = args[index + 1];
|
|
622
|
+
if (!/^[1-9]\d*$/.test(String(value ?? ''))) {
|
|
623
|
+
throw new TypeError('--shards must be a positive integer resource budget');
|
|
624
|
+
}
|
|
625
|
+
budget = Number(value);
|
|
626
|
+
if (!Number.isSafeInteger(budget)) {
|
|
627
|
+
throw new TypeError('--shards must be a positive integer resource budget');
|
|
628
|
+
}
|
|
629
|
+
index += 1;
|
|
630
|
+
}
|
|
631
|
+
return budget;
|
|
600
632
|
}
|
|
601
633
|
|
|
602
634
|
function isForgeCheckout(rootDir) {
|
|
@@ -612,17 +644,142 @@ function isForgeCheckout(rootDir) {
|
|
|
612
644
|
}
|
|
613
645
|
}
|
|
614
646
|
|
|
615
|
-
|
|
647
|
+
function isValidationRootAvailable(rootDir) {
|
|
648
|
+
try {
|
|
649
|
+
return fs.statSync(rootDir).isDirectory();
|
|
650
|
+
} catch {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function unavailableValidationRootResult(startTime) {
|
|
656
|
+
return {
|
|
657
|
+
success: false,
|
|
658
|
+
testsFound: false,
|
|
659
|
+
fullSuite: false,
|
|
660
|
+
duration: Date.now() - startTime,
|
|
661
|
+
passed: 0,
|
|
662
|
+
failed: 0,
|
|
663
|
+
total: 0,
|
|
664
|
+
message: 'Test execution failed: validation root directory is unavailable',
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function testExecutionErrorResult(error, startTime, rootDir, useFullSuite, timeout) {
|
|
669
|
+
const output = error.stdout || error.message;
|
|
670
|
+
const counts = parseTestCounts(output);
|
|
671
|
+
const budgetEvidence = counts.resourceBudget ? { resourceBudget: counts.resourceBudget } : {};
|
|
672
|
+
if (error.code === 'ETIMEDOUT' || (error.killed && error.signal === 'SIGTERM')) {
|
|
673
|
+
return {
|
|
674
|
+
success: false,
|
|
675
|
+
fullSuite: useFullSuite,
|
|
676
|
+
...budgetEvidence,
|
|
677
|
+
duration: Date.now() - startTime,
|
|
678
|
+
passed: 0,
|
|
679
|
+
failed: 0,
|
|
680
|
+
total: 0,
|
|
681
|
+
message: validationTimeoutMessage('Test execution', timeout),
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (isCommandNotFound(error)) {
|
|
686
|
+
if (!isValidationRootAvailable(rootDir)) {
|
|
687
|
+
return { ...unavailableValidationRootResult(startTime), ...budgetEvidence };
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
success: true,
|
|
691
|
+
skipped: true,
|
|
692
|
+
testsFound: false,
|
|
693
|
+
fullSuite: useFullSuite,
|
|
694
|
+
...budgetEvidence,
|
|
695
|
+
duration: Date.now() - startTime,
|
|
696
|
+
passed: 0,
|
|
697
|
+
failed: 0,
|
|
698
|
+
total: 0,
|
|
699
|
+
message: 'Tests skipped: test runner not found. Install Node.js and Bun to run tests.',
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (counts.resourceBudget?.outcome === 'rejected') {
|
|
704
|
+
return {
|
|
705
|
+
success: false,
|
|
706
|
+
testsFound: false,
|
|
707
|
+
fullSuite: useFullSuite,
|
|
708
|
+
resourceBudget: counts.resourceBudget,
|
|
709
|
+
duration: Date.now() - startTime,
|
|
710
|
+
passed: 0,
|
|
711
|
+
failed: 0,
|
|
712
|
+
total: 0,
|
|
713
|
+
message: `Full suite resource budget rejected: requested=${counts.resourceBudget.requested} minimum=${counts.resourceBudget.minimum}`,
|
|
714
|
+
output,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
const passed = counts.passed;
|
|
718
|
+
const failed = counts.failed > 0 ? counts.failed : Math.max(1, counts.errors || 0);
|
|
719
|
+
const total = counts.total > 0 ? counts.total : passed + failed + counts.skipped;
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
success: false,
|
|
723
|
+
testsFound: counts.testsFound,
|
|
724
|
+
fullSuite: useFullSuite,
|
|
725
|
+
resourceBudget: counts.resourceBudget,
|
|
726
|
+
duration: Date.now() - startTime,
|
|
727
|
+
passed,
|
|
728
|
+
failed,
|
|
729
|
+
total,
|
|
730
|
+
message: `${failed}/${total} tests failed`,
|
|
731
|
+
output,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async function runAllTests(exec = execFileSync, rootDir = process.cwd(), resourceBudget = null) {
|
|
616
736
|
const startTime = Date.now();
|
|
737
|
+
if (!isValidationRootAvailable(rootDir)) return unavailableValidationRootResult(startTime);
|
|
617
738
|
const useFullSuite = isForgeCheckout(rootDir);
|
|
739
|
+
if (resourceBudget !== null && !useFullSuite) {
|
|
740
|
+
return {
|
|
741
|
+
success: false,
|
|
742
|
+
testsFound: false,
|
|
743
|
+
fullSuite: false,
|
|
744
|
+
duration: Date.now() - startTime,
|
|
745
|
+
passed: 0,
|
|
746
|
+
failed: 0,
|
|
747
|
+
total: 0,
|
|
748
|
+
message: '--shards requires the canonical Forge full-suite runner',
|
|
749
|
+
};
|
|
750
|
+
}
|
|
618
751
|
const command = useFullSuite ? 'node' : 'bun';
|
|
619
752
|
const args = useFullSuite ? ['scripts/test-full-suite.js'] : ['test', '--timeout', '30000'];
|
|
753
|
+
if (useFullSuite && resourceBudget !== null) args.push('--shards', String(resourceBudget));
|
|
620
754
|
const execOptions = getExecOptions(rootDir, useFullSuite);
|
|
621
755
|
|
|
622
756
|
try {
|
|
623
757
|
const result = exec(command, args, execOptions); // NOSONAR S4036 - fixed CLI commands, no user input, developer tool context
|
|
624
758
|
|
|
625
|
-
const {
|
|
759
|
+
const {
|
|
760
|
+
status,
|
|
761
|
+
passed,
|
|
762
|
+
failed,
|
|
763
|
+
errors = 0,
|
|
764
|
+
skipped,
|
|
765
|
+
total,
|
|
766
|
+
testsFound,
|
|
767
|
+
resourceBudget: budgetEvidence,
|
|
768
|
+
} = parseTestCounts(result);
|
|
769
|
+
if (useFullSuite && status !== 'PASS') {
|
|
770
|
+
return {
|
|
771
|
+
success: false,
|
|
772
|
+
testsFound,
|
|
773
|
+
fullSuite: true,
|
|
774
|
+
resourceBudget: budgetEvidence,
|
|
775
|
+
duration: Date.now() - startTime,
|
|
776
|
+
passed,
|
|
777
|
+
failed,
|
|
778
|
+
skipped,
|
|
779
|
+
total,
|
|
780
|
+
message: 'Full suite did not report a passing terminal aggregate',
|
|
781
|
+
};
|
|
782
|
+
}
|
|
626
783
|
const success = failed === 0 && errors === 0 && (status === undefined || status === 'PASS');
|
|
627
784
|
|
|
628
785
|
// B2 (N1): a bun run that executes 0 tests must NEVER report PASS — that
|
|
@@ -632,6 +789,7 @@ async function runAllTests(exec = execFileSync, rootDir = process.cwd()) {
|
|
|
632
789
|
success: true,
|
|
633
790
|
skipped: true,
|
|
634
791
|
testsFound: false,
|
|
792
|
+
fullSuite: useFullSuite,
|
|
635
793
|
duration: Date.now() - startTime,
|
|
636
794
|
passed,
|
|
637
795
|
failed,
|
|
@@ -643,61 +801,17 @@ async function runAllTests(exec = execFileSync, rootDir = process.cwd()) {
|
|
|
643
801
|
return {
|
|
644
802
|
success,
|
|
645
803
|
testsFound: true,
|
|
804
|
+
fullSuite: useFullSuite,
|
|
805
|
+
resourceBudget: budgetEvidence,
|
|
646
806
|
duration: Date.now() - startTime,
|
|
647
807
|
passed,
|
|
648
808
|
failed,
|
|
649
809
|
skipped,
|
|
650
810
|
total,
|
|
651
|
-
message: success
|
|
652
|
-
? `All ${total} tests passed`
|
|
653
|
-
: `${failed + errors}/${total} tests failed`,
|
|
811
|
+
message: success ? `All ${total} tests passed` : `${failed + errors}/${total} tests failed`,
|
|
654
812
|
};
|
|
655
813
|
} catch (error) {
|
|
656
|
-
|
|
657
|
-
if (error.code === 'ETIMEDOUT' || (error.killed && error.signal === 'SIGTERM')) {
|
|
658
|
-
return {
|
|
659
|
-
success: false,
|
|
660
|
-
duration: Date.now() - startTime,
|
|
661
|
-
passed: 0,
|
|
662
|
-
failed: 0,
|
|
663
|
-
total: 0,
|
|
664
|
-
message: validationTimeoutMessage('Test execution', execOptions.timeout),
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Test runner absent — say so explicitly (do NOT silently pass).
|
|
669
|
-
if (isCommandNotFound(error)) {
|
|
670
|
-
return {
|
|
671
|
-
success: true,
|
|
672
|
-
skipped: true,
|
|
673
|
-
testsFound: false,
|
|
674
|
-
duration: Date.now() - startTime,
|
|
675
|
-
passed: 0,
|
|
676
|
-
failed: 0,
|
|
677
|
-
total: 0,
|
|
678
|
-
message: 'Tests skipped: test runner not found. Install Node.js and Bun to run tests.',
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Parse test failures from output
|
|
683
|
-
const output = error.stdout || error.message;
|
|
684
|
-
const counts = parseTestCounts(output);
|
|
685
|
-
const passed = counts.passed;
|
|
686
|
-
// bun exited non-zero: if the output shows no explicit failure count, treat
|
|
687
|
-
// it as at least one failure rather than swallowing the non-zero exit.
|
|
688
|
-
const failed = counts.failed > 0 ? counts.failed : Math.max(1, counts.errors || 0);
|
|
689
|
-
const total = counts.total > 0 ? counts.total : passed + failed + counts.skipped;
|
|
690
|
-
|
|
691
|
-
return {
|
|
692
|
-
success: false,
|
|
693
|
-
testsFound: counts.testsFound,
|
|
694
|
-
duration: Date.now() - startTime,
|
|
695
|
-
passed,
|
|
696
|
-
failed,
|
|
697
|
-
total,
|
|
698
|
-
message: `${failed}/${total} tests failed`,
|
|
699
|
-
output,
|
|
700
|
-
};
|
|
814
|
+
return testExecutionErrorResult(error, startTime, rootDir, useFullSuite, execOptions.timeout);
|
|
701
815
|
}
|
|
702
816
|
}
|
|
703
817
|
|
|
@@ -723,13 +837,27 @@ async function runAllTests(exec = execFileSync, rootDir = process.cwd()) {
|
|
|
723
837
|
* console.log(result.summary);
|
|
724
838
|
*/
|
|
725
839
|
async function executeValidate(options = {}) { // NOSONAR S3776
|
|
726
|
-
const {
|
|
840
|
+
const {
|
|
841
|
+
skip = [],
|
|
842
|
+
continueOnError = true,
|
|
843
|
+
rootDir = process.cwd(),
|
|
844
|
+
resourceBudget = null,
|
|
845
|
+
} = options || {};
|
|
846
|
+
const startTime = Date.now();
|
|
847
|
+
if (!isValidationRootAvailable(rootDir)) {
|
|
848
|
+
const checks = { tests: unavailableValidationRootResult(startTime) };
|
|
849
|
+
const result = buildResult(checks, [CHECK_TYPES.TESTS], [], startTime);
|
|
850
|
+
result.validationReceipt = false;
|
|
851
|
+
return result;
|
|
852
|
+
}
|
|
853
|
+
const receiptApi = options?.validationReceipt || validationReceipt;
|
|
854
|
+
const testRunner = options?.runAllTests || runAllTests;
|
|
855
|
+
const validationSnapshot = receiptApi.beginValidation(rootDir);
|
|
856
|
+
const receiptSnapshot = skip.length === 0 ? validationSnapshot : null;
|
|
727
857
|
|
|
728
858
|
const checks = {};
|
|
729
859
|
const failedChecks = [];
|
|
730
860
|
const errors = [];
|
|
731
|
-
const startTime = Date.now();
|
|
732
|
-
|
|
733
861
|
// 0. Conflict markers
|
|
734
862
|
if (!skip.includes(CHECK_TYPES.CONFLICT_MARKERS)) {
|
|
735
863
|
try {
|
|
@@ -797,7 +925,7 @@ async function executeValidate(options = {}) { // NOSONAR S3776
|
|
|
797
925
|
// 4. Tests
|
|
798
926
|
if (!skip.includes(CHECK_TYPES.TESTS)) {
|
|
799
927
|
try {
|
|
800
|
-
checks.tests = await
|
|
928
|
+
checks.tests = await testRunner(execFileSync, rootDir, resourceBudget);
|
|
801
929
|
if (!checks.tests.success && checks.tests.skipped !== true) {
|
|
802
930
|
failedChecks.push(CHECK_TYPES.TESTS);
|
|
803
931
|
if (!continueOnError) {
|
|
@@ -810,7 +938,9 @@ async function executeValidate(options = {}) { // NOSONAR S3776
|
|
|
810
938
|
}
|
|
811
939
|
}
|
|
812
940
|
|
|
813
|
-
|
|
941
|
+
const result = buildResult(checks, failedChecks, errors, startTime);
|
|
942
|
+
result.validationReceipt = receiptApi.completeValidation(rootDir, receiptSnapshot, result);
|
|
943
|
+
return result;
|
|
814
944
|
}
|
|
815
945
|
|
|
816
946
|
/**
|
|
@@ -885,21 +1015,43 @@ function executeDebugMode({ fixAttempts = 0, claim } = {}) {
|
|
|
885
1015
|
module.exports = {
|
|
886
1016
|
name: 'validate',
|
|
887
1017
|
description: 'Run validation checks across type, lint, security, and tests',
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1018
|
+
usage: 'forge validate [--shards N]',
|
|
1019
|
+
flags: {
|
|
1020
|
+
'--shards N': 'Set the full-suite resource budget',
|
|
1021
|
+
},
|
|
1022
|
+
handler: async (args = [], _flags = {}, projectRoot = process.cwd(), deps = {}) => {
|
|
1023
|
+
let resourceBudget;
|
|
1024
|
+
try {
|
|
1025
|
+
resourceBudget = parseResourceBudget(args);
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return { success: false, error: error.message };
|
|
892
1028
|
}
|
|
893
|
-
|
|
1029
|
+
const result = await (deps.executeValidate || executeValidate)({
|
|
1030
|
+
rootDir: projectRoot,
|
|
1031
|
+
resourceBudget,
|
|
1032
|
+
});
|
|
1033
|
+
const errorParts = Array.isArray(result.errors) ? result.errors.filter(Boolean) : [];
|
|
1034
|
+
for (const check of Object.values(result.checks || {})) {
|
|
1035
|
+
if (check?.success !== false || check.skipped === true || !check.message) continue;
|
|
1036
|
+
if (!errorParts.some(error => error.endsWith(check.message))) errorParts.push(check.message);
|
|
1037
|
+
}
|
|
1038
|
+
const budget = result.checks?.tests?.resourceBudget;
|
|
1039
|
+
const budgetOutput = Number.isInteger(budget?.effective)
|
|
1040
|
+
? `Full suite resource budget: requested=${budget.requested ?? 'default'} effective=${budget.effective}`
|
|
1041
|
+
: null;
|
|
1042
|
+
const output = [result.success ? result.summary : null, budgetOutput].filter(Boolean).join('\n');
|
|
1043
|
+
const error = result.error || result.message || errorParts.join('; ') || result.summary;
|
|
894
1044
|
return {
|
|
895
1045
|
...result,
|
|
896
|
-
output:
|
|
1046
|
+
...(output ? { output } : {}),
|
|
1047
|
+
...(!result.success && error ? { error } : {}),
|
|
897
1048
|
};
|
|
898
1049
|
},
|
|
899
1050
|
runTypeCheck,
|
|
900
1051
|
runLint,
|
|
901
1052
|
runSecurityScan,
|
|
902
1053
|
runAllTests,
|
|
1054
|
+
parseResourceBudget,
|
|
903
1055
|
parseTestCounts,
|
|
904
1056
|
scanForConflictMarkers,
|
|
905
1057
|
executeValidate,
|