Add shared instruction files and copilot history maintenance scripts
- 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
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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/);
|
||||
});
|
||||
Reference in New Issue
Block a user