fix: clean generated harness residue

This commit is contained in:
Mikael Hugo 2026-05-05 15:04:34 +02:00
parent 2d9c2018af
commit 55e7dd0e02
3 changed files with 203 additions and 1 deletions

View file

@ -0,0 +1,35 @@
# Generated Artifact Policy
SF keeps operational generated artifacts out of the project root unless a human
explicitly promotes them into durable project documentation.
## Default Locations
- `.sf/` stores SF-local operational state, generated harness notes, scaffold
manifests, runtime caches, locks, and temporary agent files.
- `docs/plans/`, `docs/specs/`, and `docs/adr/` store promoted, reviewed,
durable project artifacts.
- Root files such as `AGENTS.md`, `ARCHITECTURE.md`, and `.siftignore` are
allowed only when they are part of the versioned scaffold contract.
## Harness
Generated harness material belongs under `.sf/harness/`.
Top-level `harness/` is not created by SF by default. If a project intentionally
owns a top-level harness, SF leaves it alone unless the files still carry
pending `sf-doc` markers proving they are old SF-generated residue.
## Doctor Cleanup
`/sf doctor --fix` may remove root-level generated residue only when all of the
following are true:
- the file is under a legacy SF-generated path such as `harness/specs/`;
- the file has an `sf-doc` marker;
- the marker template matches the file path;
- the marker state is `pending`;
- the current body hash still matches the marker hash.
Anything edited, completed, unmarked, or project-owned is reported conservatively
or left untouched.

View file

@ -4,8 +4,9 @@ import {
mkdirSync,
readdirSync,
readFileSync,
rmSync,
} from "node:fs";
import { join } from "node:path";
import { dirname, join } from "node:path";
import { invalidateAllCaches } from "./cache.js";
import {
checkEngineHealth,
@ -42,6 +43,7 @@ import {
sfRoot,
} from "./paths.js";
import { loadEffectiveSFPreferences } from "./preferences.js";
import { bodyHash, extractMarker } from "./scaffold-versioning.js";
import { readAllSelfFeedback, recordSelfFeedback } from "./self-feedback.js";
import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js";
import { deriveState, isMilestoneComplete } from "./state.js";
@ -53,6 +55,79 @@ const DEFAULT_STALE_PROGRESS_MS = 20 * 60 * 1000;
const DEFAULT_OPTIONAL_CHILD_BUDGET_MS = 30 * 60 * 1000;
const REPEATED_FAILURE_THRESHOLD = 3;
const FLOW_AUDIT_ROLLUP_KIND = "flow-audit:repeated-milestone-failure";
const LEGACY_ROOT_HARNESS_PATHS = [
"harness/AGENTS.md",
"harness/specs/AGENTS.md",
"harness/specs/bootstrap.md",
"harness/evals/AGENTS.md",
"harness/graders/AGENTS.md",
];
function pruneEmptyDir(path) {
try {
if (existsSync(path) && readdirSync(path).length === 0) {
rmSync(path, { recursive: false });
}
} catch {
// Best-effort cleanup only.
}
}
function collectOwnedLegacyRootHarnessFiles(basePath) {
const owned = [];
for (const relPath of LEGACY_ROOT_HARNESS_PATHS) {
const target = join(basePath, relPath);
if (!existsSync(target)) continue;
const { marker, body } = extractMarker(target);
if (!marker) continue;
if (marker.template !== relPath) continue;
if (marker.state !== "pending") continue;
if (bodyHash(body) !== marker.hash) continue;
owned.push(relPath);
}
return owned;
}
function removeOwnedLegacyRootHarnessFiles(basePath, relPaths) {
for (const relPath of relPaths) {
rmSync(join(basePath, relPath), { force: true });
}
for (const relPath of relPaths) {
let dir = dirname(join(basePath, relPath));
while (dir.startsWith(join(basePath, "harness"))) {
pruneEmptyDir(dir);
if (dir === join(basePath, "harness")) break;
dir = dirname(dir);
}
}
pruneEmptyDir(join(basePath, "harness"));
}
function checkGeneratedArtifactResidue(
basePath,
issues,
fixesApplied,
shouldFix,
) {
const ownedRootHarness = collectOwnedLegacyRootHarnessFiles(basePath);
if (ownedRootHarness.length === 0) return;
issues.push({
severity: "warning",
code: "generated_root_harness_residue",
scope: "project",
unitId: "project",
message: `Found ${ownedRootHarness.length} SF-owned generated harness file(s) under root harness/. Generated operational harness belongs under .sf/harness/; promote durable contracts to docs/specs/ explicitly.`,
file: "harness/",
fixable: true,
});
if (shouldFix("generated_root_harness_residue")) {
removeOwnedLegacyRootHarnessFiles(basePath, ownedRootHarness);
fixesApplied.push(
`removed ${ownedRootHarness.length} SF-owned root harness file(s)`,
);
}
}
function parseEpochMs(value, fallbackMs) {
if (typeof value === "number" && Number.isFinite(value)) {
return value < 10_000_000_000 ? value * 1000 : value;
@ -1024,6 +1099,7 @@ export async function runSFDoctor(basePath, options) {
});
}
}
checkGeneratedArtifactResidue(basePath, issues, fixesApplied, shouldFix);
// Git health checks — timed
const t0git = Date.now();
const isolationMode =

View file

@ -0,0 +1,91 @@
import assert from "node:assert/strict";
import {
existsSync,
mkdirSync,
mkdtempSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { afterEach, describe, test } from "vitest";
import { runSFDoctor } from "../doctor.js";
import { stampScaffoldFile } from "../scaffold-versioning.js";
const tmpDirs = [];
afterEach(() => {
while (tmpDirs.length > 0) {
const dir = tmpDirs.pop();
if (dir) rmSync(dir, { recursive: true, force: true });
}
});
function makeProject() {
const dir = mkdtempSync(join(tmpdir(), "sf-doctor-generated-"));
tmpDirs.push(dir);
mkdirSync(join(dir, ".sf"), { recursive: true });
return dir;
}
function writeOwnedLegacyHarnessFile(root, relPath) {
const target = join(root, relPath);
mkdirSync(dirname(target), { recursive: true });
writeFileSync(target, `# ${relPath}\n`, "utf-8");
stampScaffoldFile(target, relPath, "2.75.0", "pending");
}
describe("doctor generated artifact hygiene", () => {
test("runSFDoctor_reports_sf_owned_root_harness_residue", async () => {
const project = makeProject();
writeOwnedLegacyHarnessFile(project, "harness/specs/bootstrap.md");
const report = await runSFDoctor(project, { scope: "project" });
const issue = report.issues.find(
(i) => i.code === "generated_root_harness_residue",
);
assert.equal(issue?.fixable, true);
assert.equal(existsSync(join(project, "harness/specs/bootstrap.md")), true);
});
test("runSFDoctor_fix_removes_only_sf_owned_pending_root_harness", async () => {
const project = makeProject();
writeOwnedLegacyHarnessFile(project, "harness/specs/bootstrap.md");
const report = await runSFDoctor(project, {
fix: true,
fixLevel: "all",
scope: "project",
});
assert.ok(
report.fixesApplied.includes("removed 1 SF-owned root harness file(s)"),
);
assert.equal(existsSync(join(project, "harness")), false);
});
test("runSFDoctor_ignores_user_owned_root_harness", async () => {
const project = makeProject();
mkdirSync(join(project, "harness/specs"), { recursive: true });
writeFileSync(
join(project, "harness/specs/project-contract.md"),
"# Project-owned harness\n",
);
const report = await runSFDoctor(project, {
fix: true,
fixLevel: "all",
scope: "project",
});
assert.equal(
report.issues.some((i) => i.code === "generated_root_harness_residue"),
false,
);
assert.equal(
existsSync(join(project, "harness/specs/project-contract.md")),
true,
);
});
});