Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
93 lines
2.8 KiB
JavaScript
Executable File
93 lines
2.8 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
// SessionStart: maintain a stable ".harness-session" symlink in the VS Code
|
|
// multi-root workspace, pointing at the CURRENT session dir. The harness
|
|
// tickets live in that dir (TICKETS_DIR), so this keeps them visible in the
|
|
// editor for plan-gate validation. The session dir path carries the
|
|
// per-session id, so a static workspace folder entry would go stale every
|
|
// session; the .code-workspace references the stable symlink name instead and
|
|
// this hook repoints the symlink at each session start.
|
|
//
|
|
// No-op when a managed launcher owns the session (CHAT_SESSION_DIR set, it has
|
|
// its own UI) or when the repo's parent is not a VS Code multi-root workspace
|
|
// (no *.code-workspace file beside it). Never throws: a convenience feature
|
|
// must not break a session.
|
|
|
|
import {
|
|
existsSync,
|
|
lstatSync,
|
|
mkdirSync,
|
|
readdirSync,
|
|
readFileSync,
|
|
readlinkSync,
|
|
rmSync,
|
|
symlinkSync,
|
|
} from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { createHookContext } from "./lib/context.mjs";
|
|
|
|
const LINK_NAME = ".harness-session";
|
|
|
|
// A managed launcher (chat-service) owns the session and its own UI; there is
|
|
// no VS Code workspace to manage.
|
|
if (process.env.CHAT_SESSION_DIR) process.exit(0);
|
|
|
|
let input = {};
|
|
try {
|
|
input = JSON.parse(readFileSync(0, "utf8"));
|
|
} catch {
|
|
// no payload, nothing to do
|
|
process.exit(0);
|
|
}
|
|
|
|
const ctx = createHookContext(input, "link-session-workspace");
|
|
|
|
const isSymlink = (p) => {
|
|
try {
|
|
return lstatSync(p).isSymbolicLink();
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
try {
|
|
// The .code-workspace file sits beside the repo (its parent dir is the
|
|
// multi-root workspace root). Only act when that layout is present.
|
|
const workspaceRoot = dirname(ctx.repo);
|
|
const inWorkspace =
|
|
existsSync(workspaceRoot) &&
|
|
readdirSync(workspaceRoot).some((f) => f.endsWith(".code-workspace"));
|
|
if (!inWorkspace) {
|
|
ctx.log(`no .code-workspace beside ${ctx.repo}, skip`);
|
|
process.exit(0);
|
|
}
|
|
|
|
// Never leave a dangling link: ensure the target exists first.
|
|
mkdirSync(ctx.sessionDir, { recursive: true });
|
|
|
|
const linkPath = join(workspaceRoot, LINK_NAME);
|
|
if (existsSync(linkPath) || isSymlink(linkPath)) {
|
|
if (!isSymlink(linkPath)) {
|
|
// A real file/dir is sitting there, do not clobber it.
|
|
ctx.log(`${linkPath} exists and is not a symlink, skip`);
|
|
process.exit(0);
|
|
}
|
|
let current = "";
|
|
try {
|
|
current = readlinkSync(linkPath);
|
|
} catch {
|
|
// unreadable link, replace it
|
|
}
|
|
if (current === ctx.sessionDir) {
|
|
ctx.log(`already linked to ${ctx.sessionDir}`);
|
|
process.exit(0);
|
|
}
|
|
rmSync(linkPath, { force: true });
|
|
}
|
|
symlinkSync(ctx.sessionDir, linkPath);
|
|
ctx.log(`linked ${LINK_NAME} -> ${ctx.sessionDir}`);
|
|
} catch (e) {
|
|
ctx.log(`skipped: ${e?.message ?? e}`);
|
|
}
|
|
|
|
process.exit(0);
|