Files
upstream-atomic-crm/.claude/hooks/link-session-workspace.mjs
upstream-mirror 93ecc1a6f8
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
snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:48:50 +05:00

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);