- Add 12 cross-project instruction files to resources/instructions/ - Add cleanup-copilot-history.mjs/.sh: quarantine-based history cleanup with dry-run and aggressive scope - Add restore-copilot-history.mjs/.sh: restore from quarantine by run, path, or filter - Add purge-copilot-history-quarantine.mjs/.sh: purge old quarantine runs with keep-latest guard - Add copilot-history-maintenance.test.mjs: 3 passing unit tests covering all three scripts
150 lines
5.4 KiB
JavaScript
150 lines
5.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
|
|
import { runCleanup } from "./cleanup-copilot-history.mjs";
|
|
import { runRestore } from "./restore-copilot-history.mjs";
|
|
import { runPurge } from "./purge-copilot-history-quarantine.mjs";
|
|
|
|
function makeDir(dirPath) {
|
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
return dirPath;
|
|
}
|
|
|
|
function writeFile(filePath, content = "x") {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, content, "utf8");
|
|
}
|
|
|
|
function readJson(filePath) {
|
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
}
|
|
|
|
test("cleanup writes a quarantine manifest for exact restore targeting", () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "copilot-history-cleanup-"));
|
|
const homeDir = path.join(tempRoot, "home");
|
|
const repoRoot = path.join(tempRoot, "repo");
|
|
const auditDir = makeDir(path.join(repoRoot, ".local", "audits", "forced-run"));
|
|
writeFile(path.join(auditDir, "selection-manifest.tsv"), "decision\treview_note\n");
|
|
writeFile(path.join(auditDir, "promotion-summary.md"), "summary\n");
|
|
|
|
const oldAuditDir = makeDir(path.join(repoRoot, ".local", "audits", "old-run"));
|
|
writeFile(path.join(oldAuditDir, "selection-manifest.tsv"), "decision\treview_note\n");
|
|
|
|
const workspaceArtifact = makeDir(
|
|
path.join(
|
|
homeDir,
|
|
"Library",
|
|
"Application Support",
|
|
"Code",
|
|
"User",
|
|
"workspaceStorage",
|
|
"abc123",
|
|
"chatSessions",
|
|
),
|
|
);
|
|
writeFile(path.join(workspaceArtifact, "session.jsonl"), "{}\n");
|
|
|
|
const previousHome = process.env.HOME;
|
|
process.env.HOME = homeDir;
|
|
|
|
try {
|
|
const result = runCleanup({
|
|
repoRoot,
|
|
auditDir,
|
|
completionCutoff: "2100-01-01T00:00:00.000Z",
|
|
scope: "aggressive",
|
|
quarantineDir: path.join(homeDir, ".copilot-resources-state", "quarantine", "copilot-history-cleanup"),
|
|
execute: true,
|
|
});
|
|
|
|
assert.equal(result.processed, 2);
|
|
assert.equal(Boolean(result.manifestPath), true);
|
|
|
|
const manifest = readJson(result.manifestPath);
|
|
assert.equal(manifest.rows.length, 2);
|
|
assert.deepEqual(
|
|
manifest.rows.map((row) => row.originalPath).sort(),
|
|
[
|
|
path.join(oldAuditDir),
|
|
path.join(homeDir, "Library", "Application Support", "Code", "User", "workspaceStorage", "abc123", "chatSessions"),
|
|
].sort(),
|
|
);
|
|
} finally {
|
|
process.env.HOME = previousHome;
|
|
}
|
|
});
|
|
|
|
test("restore exact path limits candidates to one original target", () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "copilot-history-restore-"));
|
|
const quarantineDir = makeDir(path.join(tempRoot, "quarantine"));
|
|
const runDir = makeDir(path.join(quarantineDir, "2026-05-24T01-21-04-653Z"));
|
|
|
|
const firstTarget = makeDir(path.join(runDir, "Users", "fragginwagon", "Library", "Application Support", "Code", "User", "workspaceStorage", "aaa", "chatSessions"));
|
|
const secondTarget = makeDir(path.join(runDir, "Users", "fragginwagon", "Library", "Application Support", "Code", "User", "workspaceStorage", "bbb", "chatEditingSessions"));
|
|
writeFile(path.join(firstTarget, "session.jsonl"), "{}\n");
|
|
writeFile(path.join(secondTarget, "state.json"), "{}\n");
|
|
fs.writeFileSync(
|
|
path.join(runDir, "manifest.json"),
|
|
JSON.stringify(
|
|
{
|
|
createdAt: "2026-05-24T01:21:04.653Z",
|
|
rows: [
|
|
{
|
|
originalPath: "/Users/fragginwagon/Library/Application Support/Code/User/workspaceStorage/aaa/chatSessions",
|
|
quarantinePath: firstTarget,
|
|
},
|
|
{
|
|
originalPath: "/Users/fragginwagon/Library/Application Support/Code/User/workspaceStorage/bbb/chatEditingSessions",
|
|
quarantinePath: secondTarget,
|
|
},
|
|
],
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
|
|
const result = runRestore({
|
|
quarantineDir,
|
|
quarantineRun: runDir,
|
|
exactPath: "/Users/fragginwagon/Library/Application Support/Code/User/workspaceStorage/aaa/chatSessions",
|
|
filter: "",
|
|
overwrite: false,
|
|
execute: false,
|
|
});
|
|
|
|
assert.equal(result.candidates, 1);
|
|
const summary = fs.readFileSync(result.summaryPath, "utf8");
|
|
assert.match(summary, /workspaceStorage\/aaa\/chatSessions/);
|
|
assert.doesNotMatch(summary, /workspaceStorage\/bbb\/chatEditingSessions/);
|
|
});
|
|
|
|
test("purge keeps newest quarantine run and selects older ones before cutoff", () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "copilot-history-purge-"));
|
|
const quarantineDir = makeDir(path.join(tempRoot, "quarantine"));
|
|
const oldRun = makeDir(path.join(quarantineDir, "2026-05-20T00-00-00-000Z"));
|
|
const newRun = makeDir(path.join(quarantineDir, "2026-05-24T00-00-00-000Z"));
|
|
writeFile(path.join(oldRun, "manifest.json"), "{}\n");
|
|
writeFile(path.join(newRun, "manifest.json"), "{}\n");
|
|
|
|
const oldTime = new Date("2026-05-20T00:00:00.000Z");
|
|
const newTime = new Date("2026-05-24T00:00:00.000Z");
|
|
fs.utimesSync(oldRun, oldTime, oldTime);
|
|
fs.utimesSync(newRun, newTime, newTime);
|
|
|
|
const result = runPurge({
|
|
quarantineDir,
|
|
before: "2026-05-23T00:00:00.000Z",
|
|
keepLatest: 1,
|
|
execute: false,
|
|
});
|
|
|
|
assert.equal(result.considered, 2);
|
|
assert.equal(result.purged, 0);
|
|
const summary = fs.readFileSync(result.summaryPath, "utf8");
|
|
assert.match(summary, /Protected by keep-latest/);
|
|
assert.match(summary, /Dry-run: no changes were applied/);
|
|
}); |