源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,496 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import test from "node:test";
|
||||
import {
|
||||
buildLaneInputFingerprint,
|
||||
gitFingerprintMaxBuffer,
|
||||
mergeLaneResults,
|
||||
resolveRetryPushReady,
|
||||
selectFailedOnlyLanes
|
||||
} from "./run-check-changed-cache.mjs";
|
||||
import {
|
||||
isPackageBuildRelevant,
|
||||
parseCliArgs,
|
||||
printSummary,
|
||||
runLanes,
|
||||
selectExistingLintFiles
|
||||
} from "./run-check-changed.mjs";
|
||||
import {
|
||||
isAgentActivityRuntimeBoundaryRelevant,
|
||||
isRendererBoundaryRelevant
|
||||
} from "./repository-checks.mjs";
|
||||
import { createIsolatedGitEnvironment } from "./git-environment.mjs";
|
||||
|
||||
isolateProcessGitEnvironment(createIsolatedGitEnvironment(tmpdir()));
|
||||
|
||||
test("parseCliArgs rejects unknown and invalid options", () => {
|
||||
assert.throws(() => parseCliArgs(["--push-reddy"]), /unknown option/u);
|
||||
assert.throws(
|
||||
() => parseCliArgs(["--max-parallel", "nope"]),
|
||||
/positive integer/u
|
||||
);
|
||||
assert.throws(() => parseCliArgs(["--base"]), /requires a value/u);
|
||||
});
|
||||
|
||||
test("parseCliArgs accepts pnpm separators and explicit values", () => {
|
||||
assert.deepEqual(
|
||||
parseCliArgs([
|
||||
"--",
|
||||
"--dry-run",
|
||||
"--push-ready",
|
||||
"--base",
|
||||
"origin/trunk",
|
||||
"--max-parallel",
|
||||
"2",
|
||||
"--tail-lines",
|
||||
"40"
|
||||
]),
|
||||
{
|
||||
baseRef: "origin/trunk",
|
||||
dryRun: true,
|
||||
failedOnly: false,
|
||||
maxParallel: 2,
|
||||
pushReady: true,
|
||||
tailLines: 40,
|
||||
verbose: false
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("package builds exclude test-only inputs", () => {
|
||||
const manifestIsNotRelevant = () => false;
|
||||
|
||||
for (const file of [
|
||||
"packages/agent/gui/controller.spec.ts",
|
||||
"packages/agent/gui/vitest.config.ts",
|
||||
"packages/agent/gui/vitest.shared.setup.ts",
|
||||
"packages/agent/gui/package.json"
|
||||
]) {
|
||||
assert.equal(
|
||||
isPackageBuildRelevant(file, manifestIsNotRelevant),
|
||||
false,
|
||||
file
|
||||
);
|
||||
}
|
||||
assert.equal(
|
||||
isPackageBuildRelevant(
|
||||
"packages/agent/gui/index.ts",
|
||||
manifestIsNotRelevant
|
||||
),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
isPackageBuildRelevant("packages/agent/gui/package.json", () => true),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test("lane input fingerprints change only with their own inputs", () => {
|
||||
const workspaceRoot = mkdtempSync(join(tmpdir(), "check-fingerprint-"));
|
||||
runFixtureGit(workspaceRoot, ["init", "--quiet"]);
|
||||
const sourcePath = join(workspaceRoot, "source.ts");
|
||||
const helperPath = join(workspaceRoot, "helper.ts");
|
||||
writeFileSync(sourcePath, "export const value = 1;\n");
|
||||
writeFileSync(helperPath, "export const helper = 1;\n");
|
||||
runFixtureGit(workspaceRoot, ["add", "source.ts", "helper.ts"]);
|
||||
runFixtureGit(workspaceRoot, [
|
||||
"-c",
|
||||
"user.email=test@example.com",
|
||||
"-c",
|
||||
"user.name=Test",
|
||||
"commit",
|
||||
"--quiet",
|
||||
"-m",
|
||||
"init"
|
||||
]);
|
||||
const sourceLane = {
|
||||
key: "source",
|
||||
label: "source",
|
||||
command: ["check", "source.ts"],
|
||||
inputFiles: ["source.ts"]
|
||||
};
|
||||
const helperLane = {
|
||||
key: "helper",
|
||||
label: "helper",
|
||||
command: ["check", "helper.ts"],
|
||||
inputFiles: ["helper.ts"]
|
||||
};
|
||||
const initialSource = buildLaneInputFingerprint({
|
||||
baseRef: "HEAD",
|
||||
lane: sourceLane,
|
||||
workspaceRoot
|
||||
});
|
||||
const initialHelper = buildLaneInputFingerprint({
|
||||
baseRef: "HEAD",
|
||||
lane: helperLane,
|
||||
workspaceRoot
|
||||
});
|
||||
|
||||
writeFileSync(sourcePath, "export const value = 2;\n");
|
||||
const workingSource = buildLaneInputFingerprint({
|
||||
baseRef: "HEAD",
|
||||
lane: sourceLane,
|
||||
workspaceRoot
|
||||
});
|
||||
const workingHelper = buildLaneInputFingerprint({
|
||||
baseRef: "HEAD",
|
||||
lane: helperLane,
|
||||
workspaceRoot
|
||||
});
|
||||
assert.notEqual(workingSource, initialSource);
|
||||
assert.equal(workingHelper, initialHelper);
|
||||
|
||||
runFixtureGit(workspaceRoot, ["add", "source.ts"]);
|
||||
writeFileSync(sourcePath, "export const value = 3;\n");
|
||||
const staged = buildLaneInputFingerprint({
|
||||
baseRef: "HEAD",
|
||||
lane: sourceLane,
|
||||
workspaceRoot
|
||||
});
|
||||
runFixtureGit(workspaceRoot, ["reset", "--quiet"]);
|
||||
const unstaged = buildLaneInputFingerprint({
|
||||
baseRef: "HEAD",
|
||||
lane: sourceLane,
|
||||
workspaceRoot
|
||||
});
|
||||
assert.notEqual(staged, unstaged);
|
||||
});
|
||||
|
||||
test("lane input fingerprints support diffs larger than the spawnSync default buffer", () => {
|
||||
const workspaceRoot = mkdtempSync(join(tmpdir(), "check-large-fingerprint-"));
|
||||
runFixtureGit(workspaceRoot, ["init", "--quiet"]);
|
||||
const sourcePath = join(workspaceRoot, "source.txt");
|
||||
writeFileSync(sourcePath, "initial\n");
|
||||
runFixtureGit(workspaceRoot, ["add", "source.txt"]);
|
||||
runFixtureGit(workspaceRoot, [
|
||||
"-c",
|
||||
"user.email=test@example.com",
|
||||
"-c",
|
||||
"user.name=Test",
|
||||
"commit",
|
||||
"--quiet",
|
||||
"-m",
|
||||
"init"
|
||||
]);
|
||||
writeFileSync(sourcePath, `${"x".repeat(2 * 1024 * 1024)}\n`);
|
||||
|
||||
const fingerprint = buildLaneInputFingerprint({
|
||||
baseRef: "HEAD",
|
||||
lane: {
|
||||
key: "large-source",
|
||||
label: "large-source",
|
||||
command: ["check", "source.txt"],
|
||||
inputFiles: ["source.txt"]
|
||||
},
|
||||
workspaceRoot
|
||||
});
|
||||
|
||||
assert.match(fingerprint, /^[a-f0-9]{64}$/u);
|
||||
});
|
||||
|
||||
test("lane fingerprint buffer covers repository-managed binary source assets", () => {
|
||||
assert.equal(gitFingerprintMaxBuffer, 128 * 1024 * 1024);
|
||||
});
|
||||
|
||||
test("failed-only reuses only passed lanes with unchanged inputs", () => {
|
||||
const currentLanes = [
|
||||
laneFixture("passed-unchanged", "same"),
|
||||
laneFixture("failed-unchanged", "same"),
|
||||
laneFixture("passed-changed", "after"),
|
||||
laneFixture("new-lane", "new")
|
||||
];
|
||||
const summary = {
|
||||
laneFingerprintVersion: 1,
|
||||
results: [
|
||||
resultFixture("passed-unchanged", "same", 0),
|
||||
resultFixture("failed-unchanged", "same", 1),
|
||||
resultFixture("passed-changed", "before", 0),
|
||||
resultFixture("removed-lane", "same", 0)
|
||||
]
|
||||
};
|
||||
|
||||
const selection = selectFailedOnlyLanes(currentLanes, summary);
|
||||
|
||||
assert.deepEqual(
|
||||
selection.lanesToRun.map((lane) => lane.key),
|
||||
["failed-unchanged", "passed-changed", "new-lane"]
|
||||
);
|
||||
assert.deepEqual(
|
||||
selection.reusedResults.map((result) => result.key),
|
||||
["passed-unchanged"]
|
||||
);
|
||||
});
|
||||
|
||||
test("failed-only rejects summaries without lane fingerprints", () => {
|
||||
assert.throws(
|
||||
() => selectFailedOnlyLanes([], { results: [] }),
|
||||
/legacy failed-lane state/u
|
||||
);
|
||||
});
|
||||
|
||||
test("failed-only inherits push-ready lanes from the previous run", () => {
|
||||
assert.equal(resolveRetryPushReady(false, { pushReady: true }), true);
|
||||
assert.equal(resolveRetryPushReady(false, { pushReady: false }), false);
|
||||
assert.equal(resolveRetryPushReady(true, { pushReady: false }), true);
|
||||
});
|
||||
|
||||
test("failed-only keeps reused results available for the next retry", () => {
|
||||
const currentLanes = [
|
||||
laneFixture("passed", "same"),
|
||||
laneFixture("retried", "fixed")
|
||||
];
|
||||
const firstSelection = selectFailedOnlyLanes(currentLanes, {
|
||||
laneFingerprintVersion: 1,
|
||||
results: [
|
||||
resultFixture("passed", "same", 0),
|
||||
resultFixture("retried", "broken", 1)
|
||||
]
|
||||
});
|
||||
const mergedResults = mergeLaneResults(
|
||||
currentLanes,
|
||||
[resultFixture("retried", "fixed", 0)],
|
||||
firstSelection.reusedResults
|
||||
);
|
||||
|
||||
const nextSelection = selectFailedOnlyLanes(currentLanes, {
|
||||
laneFingerprintVersion: 1,
|
||||
results: mergedResults
|
||||
});
|
||||
|
||||
assert.deepEqual(nextSelection.lanesToRun, []);
|
||||
assert.deepEqual(
|
||||
nextSelection.reusedResults.map((result) => result.key),
|
||||
["passed", "retried"]
|
||||
);
|
||||
});
|
||||
|
||||
test("renderer boundary lane covers renderer and checker changes", () => {
|
||||
for (const file of [
|
||||
"apps/desktop/src/renderer/src/features/workspace-workbench/services/coordinator.ts",
|
||||
"tools/scripts/check-renderer-feature-boundaries.mjs",
|
||||
"tools/scripts/check-renderer-feature-boundaries.test.mjs"
|
||||
]) {
|
||||
assert.equal(isRendererBoundaryRelevant(file), true, file);
|
||||
}
|
||||
assert.equal(
|
||||
isRendererBoundaryRelevant("apps/desktop/src/main/index.ts"),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("activity runtime boundary lane covers package, desktop adapter, and checker changes", () => {
|
||||
for (const file of [
|
||||
"packages/agent/gui/AgentGUI.tsx",
|
||||
"packages/agent/activity-core/src/engine/engine.ts",
|
||||
"apps/desktop/src/renderer/src/features/workspace-agent/services/runtime.ts",
|
||||
"apps/desktop/src/renderer/src/features/workspace-workbench/ui/Agent.tsx",
|
||||
"tools/scripts/check-agent-activity-runtime-boundaries.mjs",
|
||||
"tools/scripts/check-agent-activity-runtime-boundaries.test.mjs"
|
||||
]) {
|
||||
assert.equal(isAgentActivityRuntimeBoundaryRelevant(file), true, file);
|
||||
}
|
||||
assert.equal(
|
||||
isAgentActivityRuntimeBoundaryRelevant(
|
||||
"apps/desktop/src/renderer/src/features/workspace-file-manager/file.ts"
|
||||
),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("selectExistingLintFiles drops deleted lint targets", () => {
|
||||
const changedFiles = [
|
||||
"packages/foo/src/live.ts",
|
||||
"packages/foo/src/deleted.ts",
|
||||
"packages/foo/README.md"
|
||||
];
|
||||
|
||||
const lintFiles = selectExistingLintFiles(
|
||||
changedFiles,
|
||||
(file) => file !== "packages/foo/src/deleted.ts"
|
||||
);
|
||||
|
||||
assert.deepEqual(lintFiles, ["packages/foo/src/live.ts"]);
|
||||
});
|
||||
|
||||
test("selectExistingLintFiles keeps existing lintable paths", () => {
|
||||
const changedFiles = [
|
||||
"apps/desktop/src/main/index.ts",
|
||||
"packages/foo/src/helper.mjs"
|
||||
];
|
||||
|
||||
const lintFiles = selectExistingLintFiles(changedFiles, () => true);
|
||||
|
||||
assert.deepEqual(lintFiles, changedFiles);
|
||||
});
|
||||
|
||||
test("runLanes preserves lane indexes without relying on outer scope", async () => {
|
||||
const runDirectory = mkdtempSync(join(tmpdir(), "run-check-changed-"));
|
||||
const lanes = [
|
||||
{
|
||||
key: "lane-b",
|
||||
label: "lane-b",
|
||||
command: [process.execPath, "-e", "setTimeout(() => {}, 10)"]
|
||||
},
|
||||
{
|
||||
key: "lane-a",
|
||||
label: "lane-a",
|
||||
command: [process.execPath, "-e", ""]
|
||||
}
|
||||
];
|
||||
|
||||
const results = await runLanes(lanes, runDirectory);
|
||||
|
||||
assert.deepEqual(
|
||||
results.map((result) => result.index),
|
||||
[0, 1]
|
||||
);
|
||||
assert.deepEqual(
|
||||
results.map((result) => result.key),
|
||||
["lane-b", "lane-a"]
|
||||
);
|
||||
});
|
||||
|
||||
test("runLanes spawns Windows command shims through the shell", async () => {
|
||||
if (process.platform !== "win32") {
|
||||
return;
|
||||
}
|
||||
|
||||
const runDirectory = mkdtempSync(join(tmpdir(), "run-check-changed-"));
|
||||
const shimPath = join(runDirectory, "shim.cmd");
|
||||
writeFileSync(shimPath, "@echo off\r\nexit /b 0\r\n");
|
||||
|
||||
const results = await runLanes(
|
||||
[
|
||||
{
|
||||
key: "windows-shim",
|
||||
label: "windows-shim",
|
||||
command: [shimPath]
|
||||
}
|
||||
],
|
||||
runDirectory
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
results.map((result) => result.exitCode),
|
||||
[0]
|
||||
);
|
||||
});
|
||||
|
||||
test("runLanes serializes lanes that write the same generated assets", async () => {
|
||||
const runDirectory = mkdtempSync(join(tmpdir(), "run-check-changed-"));
|
||||
const markerPath = join(runDirectory, "marker.txt");
|
||||
const firstProgram =
|
||||
'const fs=require("node:fs"); fs.appendFileSync(process.argv[1], "first-start\\n"); setTimeout(() => { fs.appendFileSync(process.argv[1], "first-end\\n"); }, 30);';
|
||||
const secondProgram =
|
||||
'const fs=require("node:fs"); if (!fs.readFileSync(process.argv[1], "utf8").includes("first-end")) process.exit(1);';
|
||||
const lanes = [
|
||||
{
|
||||
key: "generate:builtin-apps",
|
||||
label: "generate:builtin-apps",
|
||||
serialGroup: "tuttid-builtin-assets",
|
||||
command: [process.execPath, "-e", firstProgram, markerPath]
|
||||
},
|
||||
{
|
||||
key: "build:go",
|
||||
label: "build:go",
|
||||
serialGroup: "tuttid-builtin-assets",
|
||||
command: [process.execPath, "-e", secondProgram, markerPath]
|
||||
}
|
||||
];
|
||||
|
||||
const results = await runLanes(lanes, runDirectory);
|
||||
|
||||
assert.deepEqual(
|
||||
results.map((result) => result.exitCode),
|
||||
[0, 0]
|
||||
);
|
||||
assert.equal(readFileSync(markerPath, "utf8"), "first-start\nfirst-end\n");
|
||||
});
|
||||
|
||||
test("printSummary includes rerun hint for failures", () => {
|
||||
const errors = [];
|
||||
const originalError = console.error;
|
||||
console.error = (...args) => {
|
||||
errors.push(args.join(" "));
|
||||
};
|
||||
|
||||
try {
|
||||
printSummary(
|
||||
[
|
||||
{
|
||||
command: ["pnpm", "lint"],
|
||||
durationMs: 10,
|
||||
exitCode: 1,
|
||||
index: 0,
|
||||
key: "lint:changed",
|
||||
label: "lint:changed",
|
||||
logPath: "/tmp/lint.log",
|
||||
logPathRelative: ".tmp/check-runs/example/lint.log"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
command: ["pnpm", "lint"],
|
||||
durationMs: 10,
|
||||
exitCode: 1,
|
||||
index: 0,
|
||||
key: "lint:changed",
|
||||
label: "lint:changed",
|
||||
logPath: "/tmp/lint.log",
|
||||
logPathRelative: ".tmp/check-runs/example/lint.log"
|
||||
}
|
||||
],
|
||||
10,
|
||||
"/tmp/check-runs/example"
|
||||
);
|
||||
} finally {
|
||||
console.error = originalError;
|
||||
}
|
||||
|
||||
assert.match(
|
||||
errors.at(-1) ?? "",
|
||||
/Rerun failed lanes with: pnpm check:changed -- --failed-only/u
|
||||
);
|
||||
});
|
||||
|
||||
function runFixtureGit(workspaceRoot, args) {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: workspaceRoot,
|
||||
encoding: "utf8",
|
||||
env: createIsolatedGitEnvironment(workspaceRoot)
|
||||
});
|
||||
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||
}
|
||||
|
||||
function laneFixture(key, inputFingerprint) {
|
||||
return {
|
||||
command: ["check", key],
|
||||
inputFiles: [`${key}.ts`],
|
||||
inputFingerprint,
|
||||
key,
|
||||
label: key
|
||||
};
|
||||
}
|
||||
|
||||
function resultFixture(key, inputFingerprint, exitCode) {
|
||||
return {
|
||||
...laneFixture(key, inputFingerprint),
|
||||
durationMs: 10,
|
||||
exitCode,
|
||||
index: 0,
|
||||
logPath: `/tmp/${key}.log`,
|
||||
logPathRelative: `.tmp/${key}.log`
|
||||
};
|
||||
}
|
||||
|
||||
function isolateProcessGitEnvironment(nextEnvironment) {
|
||||
for (const name of Object.keys(process.env)) {
|
||||
if (!Object.hasOwn(nextEnvironment, name)) {
|
||||
delete process.env[name];
|
||||
}
|
||||
}
|
||||
Object.assign(process.env, nextEnvironment);
|
||||
}
|
||||
Reference in New Issue
Block a user