mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-09-14 14:01:28 +08:00
f35c545fbe
* feat(metaharness): pull in @metaharness/turn-credit + fix stale router/darwin pins (post metaharness#176) metaharness#176 shipped @metaharness/turn-credit (ADR-248, recursive turn-level credit assignment) and bumped darwin to 0.9.0 (ADR-249 signal seams) + router to 0.4.0 (calibration module). This brings ruflo's dependency contract back in sync and makes the new package available: - Add @metaharness/turn-credit ~0.1.0 as a new optionalDependency, following the same "must be installable, not peer-only" pattern darwin/flywheel/radio already use (ADR-150) — dependency-free, 64.9K unpacked, zero lifecycle scripts, same profile as the other three. - Bump @metaharness/darwin ~0.8.3 -> ~0.9.0 and the paired MH_DARWIN_PIN constant in distill-oracle.ts (was already out of range: tilde only absorbs patches, and 0.9.0 is a minor bump). - Bump @metaharness/router peer range ^0.3.2 -> ^0.4.0 (still deliberately peer-only + triple-gated behind CLAUDE_FLOW_ROUTER_NEURAL=1, per neural-router.ts — that design choice is unchanged, only the stale range is fixed) and update the matching manual-install hint in neural.ts. - scripts/check-metaharness-pins.mjs + scripts/metaharness-clean-install-test.mjs: add turn-credit to the watched/contract-checked package list. - .github/workflows/no-cli-optdep-bloat-2561.yml: CLI_MAX 10 -> 13. The prior bump (PR #2956) left zero slack (budget == count exactly), which a code review flagged as a latent trap — the very next unrelated optional dep would trip this guard. This bump leaves real headroom (11 declared today, budget 13) instead of repeating that mistake. - Also fixes a live ReferenceError in distill-oracle.test.ts (MH_DARWIN_PIN used but never imported) — a gap from the prior release's test fix that somehow didn't surface in that PR's CI; caught here while touching the same file. Co-Authored-By: RuFlo <ruv@ruv.net> * fix: regenerate v3/pnpm-lock.yaml — was out of sync with package.json edits The previous commit edited v3/@claude-flow/cli/package.json directly (darwin/router/turn-credit pin changes) without regenerating the pnpm workspace lockfile, so every CI job running `pnpm install --frozen-lockfile` failed immediately with ERR_PNPM_OUTDATED_LOCKFILE — cascading into every downstream smoke/test job that depends on that install step. Regenerated with pnpm@8.15.9 (matching CI's pinned version) in an isolated worktree to avoid the lockfile-version drift a newer local pnpm would introduce. Co-Authored-By: RuFlo <ruv@ruv.net>
109 lines
5.2 KiB
JavaScript
109 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// metaharness-clean-install-test — the dependency-contract acceptance gate.
|
|
//
|
|
// WHY: the MetaHarness packages were once declared only as OPTIONAL PEER
|
|
// dependencies, which npm never auto-installs — so a clean `npm install` of
|
|
// ruflo shipped with zero MetaHarness packages on disk while every advertised
|
|
// surface (`ruflo metaharness …`) assumed they might be there. The pin watcher
|
|
// missed it because it never looked at peerDependencies. This script closes
|
|
// the loop end-to-end: it installs the EXACT ranges v3/@claude-flow/cli
|
|
// declares into a pristine temp project (no repo node_modules in scope), then
|
|
// imports each package and asserts the advertised symbol contract.
|
|
//
|
|
// USAGE
|
|
// node scripts/metaharness-clean-install-test.mjs # human output
|
|
// node scripts/metaharness-clean-install-test.mjs --format json
|
|
//
|
|
// EXIT CODES
|
|
// 0 every declared installable pin installs cleanly and exports its contract
|
|
// 1 a pin is missing from optionalDependencies, fails to install, or is
|
|
// missing an advertised export
|
|
// 2 unexpected error (temp-dir creation, package.json unreadable, …)
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const CLI_PKG = join(HERE, '..', 'v3', '@claude-flow', 'cli', 'package.json');
|
|
const FORMAT = process.argv.includes('--format') && process.argv[process.argv.indexOf('--format') + 1] === 'json' ? 'json' : 'table';
|
|
|
|
// Advertised symbol contract per package — verified against the real published
|
|
// artifacts (darwin 0.9.0 / flywheel 0.1.10 / radio 0.1.0 / turn-credit 0.1.0).
|
|
// Keep in sync with the --require-installed check in check-metaharness-pins.mjs.
|
|
const API_CONTRACT = {
|
|
'@metaharness/darwin': ['evolve', 'RefineMutator', 'summarizeFailedTraces'],
|
|
'@metaharness/flywheel': ['runFlywheelGenerations', 'meetsPromotionRule', 'makeSigner', 'verifyReplayBundle', 'sequentialEvidence', 'withSequentialEvidence'],
|
|
'@metaharness/radio': ['RadioBus', 'runProtocol', 'runSim'],
|
|
'@metaharness/turn-credit': ['processTrajectory', 'creditByLabel', 'evidenceFromLogProbs', 'buildCreditReceiptPayload'],
|
|
};
|
|
|
|
async function main() {
|
|
const pkg = JSON.parse(readFileSync(CLI_PKG, 'utf-8'));
|
|
const results = [];
|
|
let failed = false;
|
|
|
|
const specs = [];
|
|
for (const name of Object.keys(API_CONTRACT)) {
|
|
const range = pkg.optionalDependencies?.[name];
|
|
if (!range) {
|
|
results.push({ name, status: 'FAIL', note: 'not declared in optionalDependencies — a clean install never materializes it' });
|
|
failed = true;
|
|
continue;
|
|
}
|
|
specs.push({ name, range });
|
|
}
|
|
|
|
let workDir = null;
|
|
if (specs.length) {
|
|
workDir = mkdtempSync(join(tmpdir(), 'ruflo-mh-clean-install-'));
|
|
writeFileSync(join(workDir, 'package.json'), JSON.stringify({ name: 'mh-clean-install-probe', private: true, type: 'module' }, null, 2));
|
|
try {
|
|
execFileSync('npm', [
|
|
'install', '--no-audit', '--no-fund', '--no-save',
|
|
...specs.map((s) => `${s.name}@${s.range}`),
|
|
], { cwd: workDir, stdio: 'pipe', timeout: 300_000 });
|
|
} catch (error) {
|
|
for (const s of specs) results.push({ name: s.name, range: s.range, status: 'FAIL', note: `npm install failed: ${String(error.message).slice(0, 200)}` });
|
|
failed = true;
|
|
specs.length = 0;
|
|
}
|
|
}
|
|
|
|
for (const s of specs) {
|
|
try {
|
|
const mod = await import(pathToFileURL(join(workDir, 'node_modules', s.name, 'dist', 'index.js')).href);
|
|
const missing = API_CONTRACT[s.name].filter((sym) => typeof mod[sym] !== 'function');
|
|
if (missing.length) {
|
|
results.push({ name: s.name, range: s.range, status: 'FAIL', note: `missing exports: ${missing.join(', ')}` });
|
|
failed = true;
|
|
} else {
|
|
results.push({ name: s.name, range: s.range, status: 'PASS', note: `${API_CONTRACT[s.name].length} advertised exports present` });
|
|
}
|
|
} catch (error) {
|
|
results.push({ name: s.name, range: s.range, status: 'FAIL', note: `import failed: ${String(error.message).slice(0, 200)}` });
|
|
failed = true;
|
|
}
|
|
}
|
|
|
|
if (workDir) { try { rmSync(workDir, { recursive: true, force: true }); } catch { /* best-effort */ } }
|
|
|
|
if (FORMAT === 'json') {
|
|
console.log(JSON.stringify({ generatedAt: new Date().toISOString(), pass: !failed, results }, null, 2));
|
|
} else {
|
|
console.log('# metaharness-clean-install-test\n');
|
|
console.log('| Package | Declared range | Status | Note |');
|
|
console.log('|---|---|---|---|');
|
|
for (const r of results) console.log(`| ${r.name} | ${r.range ?? '—'} | ${r.status} | ${r.note} |`);
|
|
console.log('');
|
|
console.log(failed
|
|
? '✗ Clean-install contract BROKEN — an advertised MetaHarness surface would degrade on a fresh install.'
|
|
: '✓ Every advertised MetaHarness package installs from a clean directory and exports its contract.');
|
|
}
|
|
process.exit(failed ? 1 : 0);
|
|
}
|
|
|
|
main().catch((e) => { console.error('metaharness-clean-install-test crashed:', e?.message || e); process.exit(2); });
|