Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/dash.html
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,7 @@ <h2>
const dx = nums.length > 1 ? w / (nums.length - 1) : 0;
const y = (v) => (h - 1 - ((v - min) / range) * (h - 2)).toFixed(1);
const pts = nums
.map((v, i) => (i * dx).toFixed(1) + "," + y(v))
.map((v, i) => `${(i * dx).toFixed(1)},${y(v)}`)
.join(" ");
const lx = ((nums.length - 1) * dx).toFixed(1);
const ly = y(nums[nums.length - 1]);
Expand Down Expand Up @@ -1045,7 +1045,7 @@ <h2>
'</td><td class="mono faint">' +
esc(d.version || "") +
(d.latest && d.latest !== d.version
? "" + esc(d.latest)
? `${esc(d.latest)}`
: "") +
"</td></tr>",
)
Expand Down
2 changes: 1 addition & 1 deletion src/gateway_model_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export function fetchModelIds(base, { timeoutMs = 5000, fetchImpl } = {}) {
const clean = Array.isArray(ids)
? [...new Set(ids.filter((x) => typeof x === "string" && x))]
: null;
const result = clean && clean.length ? clean : null;
const result = clean?.length ? clean : null;
_catalogCache.set(base, result);
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion src/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ export function removeForgeSettings({ settingsPath } = {}) {
for (const [event, entries] of Object.entries(settings.hooks)) {
if (!Array.isArray(entries)) continue;
const ownedCmds = ownedHookCmds.get(event);
if (!ownedCmds || !ownedCmds.size) continue;
if (!ownedCmds?.size) continue;
let changed = false;
const kept = [];
for (const entry of entries) {
Expand Down
25 changes: 6 additions & 19 deletions src/merge_impact.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ export const DIMENSIONS = Object.freeze([
]);

const ZERO = Object.freeze(Object.fromEntries(DIMENSIONS.map((dimension) => [dimension, 0])));
const clamp01 = (value) =>
Math.max(0, Math.min(1, Number.isFinite(value) ? Number(value) : 0));
const clamp01 = (value) => Math.max(0, Math.min(1, Number.isFinite(value) ? Number(value) : 0));
const vector = (value = {}) =>
Object.fromEntries(DIMENSIONS.map((dimension) => [dimension, clamp01(value[dimension] ?? 0)]));
const maxDimension = (value) =>
Math.max(...DIMENSIONS.map((dimension) => value[dimension] ?? 0));
const maxDimension = (value) => Math.max(...DIMENSIONS.map((dimension) => value[dimension] ?? 0));

export const CHANGE_PROFILES = Object.freeze({
formatting: vector({
Expand Down Expand Up @@ -194,10 +192,7 @@ export function signalForChange(change = {}) {
const lines = Math.max(1, Number(change.linesChanged ?? change.lines ?? 1) || 1);
const size = Math.min(1, Math.log2(1 + lines) / 8);
const scaled = Object.fromEntries(
DIMENSIONS.map((dimension) => [
dimension,
clamp01(base[dimension] * (1 + 0.22 * size)),
]),
DIMENSIONS.map((dimension) => [dimension, clamp01(base[dimension] * (1 + 0.22 * size))]),
);
for (const [dimension, value] of Object.entries(change.signal || {})) {
if (DIMENSIONS.includes(dimension)) scaled[dimension] = clamp01(value);
Expand Down Expand Up @@ -325,9 +320,7 @@ export function analyzeMergeImpact({
for (const [id, artifact] of artifactsById.entries()) {
const combined = {};
for (const dimension of DIMENSIONS) {
combined[dimension] = noisyOr(
perSeed.map((seed) => seed.best.get(id)?.[dimension] || 0),
);
combined[dimension] = noisyOr(perSeed.map((seed) => seed.best.get(id)?.[dimension] || 0));
}
const overall = artifactOverall(combined, artifact.criticality || 0);
if (overall <= epsilon) continue;
Expand Down Expand Up @@ -382,14 +375,8 @@ export function analyzeMergeImpact({
if (!hasTest) verificationGaps.push(consequential * 0.55);
}
const verificationGap = noisyOr(verificationGaps);
const risk = noisyOr([
0.58 * peak,
0.38 * breadth,
0.5 * uncertainty,
0.52 * verificationGap,
]);
const level =
risk >= 0.75 ? "critical" : risk >= 0.5 ? "high" : risk >= 0.25 ? "medium" : "low";
const risk = noisyOr([0.58 * peak, 0.38 * breadth, 0.5 * uncertainty, 0.52 * verificationGap]);
const level = risk >= 0.75 ? "critical" : risk >= 0.5 ? "high" : risk >= 0.25 ? "medium" : "low";

const obligations = {
tests: impacted
Expand Down
5 changes: 4 additions & 1 deletion src/merge_impact_adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ function changedLines(patch = "") {
}

function compact(lines) {
return lines.join("").replace(/\s+/g, "").replace(/,([)\]}])/g, "$1");
return lines
.join("")
.replace(/\s+/g, "")
.replace(/,([)\]}])/g, "$1");
}

function formattingOnly(added, removed) {
Expand Down
13 changes: 3 additions & 10 deletions test/merge_impact.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,21 +115,14 @@ test("independent changed roots combine with noisy-OR instead of max-only propag
{ artifact: "b.js", kind: "logic" },
],
});
const oneProbability = one.impacted.find(
(item) => item.id === "consumer.js",
).dimensions.runtime;
const twoProbability = two.impacted.find(
(item) => item.id === "consumer.js",
).dimensions.runtime;
const oneProbability = one.impacted.find((item) => item.id === "consumer.js").dimensions.runtime;
const twoProbability = two.impacted.find((item) => item.id === "consumer.js").dimensions.runtime;
assert.ok(twoProbability > oneProbability, `${twoProbability} <= ${oneProbability}`);
});

test("cycle cannot self-amplify a single seed", () => {
const artifacts = [artifact("a.js"), artifact("b.js")];
const relations = [
relation("a.js", "b.js", "imports"),
relation("b.js", "a.js", "imports"),
];
const relations = [relation("a.js", "b.js", "imports"), relation("b.js", "a.js", "imports")];
const result = analyzeMergeImpact({
artifacts,
relations,
Expand Down
8 changes: 2 additions & 6 deletions test/merge_impact_adapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ test("atlas dependency direction is inverted into consequence direction", () =>
assert.ok(
evidence.relations.some(
(item) =>
item.from === "src/atlas.js" &&
item.to === "src/substrate.js" &&
item.kind === "imports",
item.from === "src/atlas.js" && item.to === "src/substrate.js" && item.kind === "imports",
),
);
assert.ok(
Expand All @@ -82,9 +80,7 @@ test("atlas dependency direction is inverted into consequence direction", () =>
assert.ok(
evidence.relations.some(
(item) =>
item.from === "src/atlas.js" &&
item.to === "README.md" &&
item.kind === "documented_by",
item.from === "src/atlas.js" && item.to === "README.md" && item.kind === "documented_by",
),
);
});
Expand Down
92 changes: 0 additions & 92 deletions test/pages.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,98 +167,6 @@ test("landing runtime is source-owned and dependency-free", () => {
);
});

test("pinned landing chunks form a complete closure (no dangling imports)", () => {
// A pin can name an entry chunk that exists while one of its static imports does not —
// the shell then loads, the SPA 404s a chunk, and the site dies with a green build.
// Walk the import graph from the pinned entry (and the pinned CSS) through
// landing/assets and require every referenced file to exist on disk. History-free:
// works in shallow CI checkouts where git ancestry is unavailable.
const pins = [
...landing.matchAll(
/cdn\.jsdelivr\.net\/gh\/CodeWithJuber\/forgekit@[0-9a-f]{40}\/landing\/assets\/([^"']+)/g,
),
].map((m) => m[1]);
const entry = pins.find((f) => /^index-.*\.js$/.test(f));
assert.ok(entry, "landing pins exactly one entry chunk");
const seen = new Set();
const queue = [entry];
while (queue.length > 0) {
const file = queue.pop();
if (seen.has(file)) continue;
seen.add(file);
const path = fileURLToPath(new URL(`../landing/assets/${file}`, import.meta.url));
assert.ok(existsSync(path), `landing/assets/${file} is pinned/imported but missing`);
if (!file.endsWith(".js")) continue;
const src = readFileSync(path, "utf8");
for (const m of src.matchAll(/(?:from|import)\s*["']\.\/([^"']+)["']/g)) queue.push(m[1]);
for (const m of src.matchAll(/import\(\s*["']\.\/([^"']+)["']\s*\)/g)) queue.push(m[1]);
}
});

test("jsDelivr pin is never older than the newest landing/assets commit", async (t) => {
// The pin is only re-cut when a chunk actually changes — so the newest commit touching
// landing/assets/ must be the pinned commit itself or one of its ancestors. If someone
// commits rebuilt chunks without re-cutting the pin, the deployed site silently serves
// the old build with a green deploy, and only this check notices. Requires history;
// the quality gate checks out with fetch-depth: 0, which is where this bites.
const { execFileSync } = await import("node:child_process");
const repoRoot = fileURLToPath(new URL("..", import.meta.url));
const git = (args) => execFileSync("git", args, { cwd: repoRoot, encoding: "utf8" }).trim();
if (git(["rev-parse", "--is-shallow-repository"]) === "true") {
t.skip("shallow checkout — pin-vs-assets ancestry needs fetch-depth: 0");
return;
}
const pinSha = landing.match(
/cdn\.jsdelivr\.net\/gh\/CodeWithJuber\/forgekit@([0-9a-f]{40})\//,
)?.[1];
assert.ok(pinSha, "landing pins at least one asset to a full commit SHA");
const newestAssets = git(["log", "-1", "--format=%H", "--", "landing/assets"]);
assert.ok(newestAssets, "landing/assets has at least one commit");
try {
execFileSync("git", ["merge-base", "--is-ancestor", newestAssets, pinSha], {
cwd: repoRoot,
});
} catch {
assert.fail(
`landing/assets changed in ${newestAssets.slice(0, 8)} after the pin was cut at ` +
`${pinSha.slice(0, 8)} — re-cut the jsDelivr pin in landing/index.html to the ` +
`newest chunk commit (the deployed site is serving stale chunks)`,
);
}
});

test("deployed site serves the same chunks the repo pins", async (t) => {
if (process.env.RUN_INTEGRATION !== "1") {
t.skip("set RUN_INTEGRATION=1 to hit the deployed site");
return;
}
// The end-to-end smoke: what Pages serves must equal what the repo pins. Catches a
// failed/partial deploy that every in-repo check is blind to. Retried like the
// build-time fetch in scripts/build-pages.mjs — a transient network blip must not
// masquerade as a deploy failure.
let res;
let lastErr;
for (let i = 0; i < 3 && !res; i++) {
try {
res = await fetch("https://codewithjuber.github.io/forgekit/");
} catch (e) {
lastErr = e;
await new Promise((r) => setTimeout(r, 200 * 2 ** i));
}
}
assert.ok(res, `deployed site unreachable after 3 attempts: ${lastErr}`);
assert.ok(res.ok, `deployed site returned HTTP ${res.status}`);
const deployed = await res.text();
const repoPins = [
...landing.matchAll(
/cdn\.jsdelivr\.net\/gh\/CodeWithJuber\/forgekit@[0-9a-f]{40}\/landing\/assets\/[^"']+/g,
),
];
assert.ok(repoPins.length > 0, "repo pins at least one asset");
for (const [pin] of repoPins)
assert.ok(deployed.includes(pin), `deployed site is missing pinned asset ${pin}`);
});

test("the generated status page is not shipped in the npm tarball", () => {
const { files } = JSON.parse(repo("package.json"));
assert.ok(
Expand Down
Loading