A session log that parallel agents never conflict on
@slashclone/parallel-session-log
Keep a project log that several Claude Code sessions can write at once without merge conflicts. Each session writes its own dated file, an index is generated instead of hand edited, and a small CI check makes sure every entry carries the line your team requires. Writes the files for you to review; never commits or pushes.
SkillDevOpsv0.1.0 (latest)
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1#!/usr/bin/env node
- 2// Fails when a change adds a session log without the required line.
- 3//
- 4// Sessions log in their own file, docs/progress/YYYY-MM-DD-slug.md. Each file added or changed
- 5// needs that name and the required line. Entries still added to the old single log,
- 6// docs/PROGRESS.md, are checked the same way, so pull requests opened before the switch pass.
- 7//
- 8// Usage: node .github/scripts/check-session-log.mjs <base-ref>
- 9// Compares the files at <base-ref> with the working tree. Node standard library only.
- 10// Edit LOG, DIR, LABEL and the headings in IGNORED to match your project.
- 11import { execFileSync } from "node:child_process";
- 12import { existsSync, readFileSync } from "node:fs";
- 14const LOG = "docs/PROGRESS.md";
- 15const DIR = "docs/progress";
- 16const LABEL = "Summary"; // the required line is "**Summary:** ..." with some text after it
- 17const IGNORED = ["Template", "Sessions", "History"]; // headings in the old log that are not entries
- 18const NAME = /^(\d{4}-\d{2}-\d{2})-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/;
- 19const REQUIRED = new RegExp(`^\\*\\*${LABEL}:\\*\\*\\s*\\S`, "m");
- 20const base = process.argv[2];
- 21if (!base) {
- 22 console.error("usage: check-session-log.mjs <base-ref>");
- 23 process.exit(2);
- 24}
- 26const lines = (args) => execFileSync("git", args, { encoding: "utf8" }).split("\n").filter(Boolean);
- 27const problems = [];
- 29// 1. Session files: every file added or changed in the folder (the template aside).
- 30const paths = new Set([
- 31 ...lines(["diff", "--name-only", "--diff-filter=AMR", base, "--", `${DIR}/`]),
- 32 ...lines(["ls-files", "--others", "--exclude-standard", "--", `${DIR}/`]),
- 33]);
- 34for (const path of paths) {
- 35 const name = path.slice(DIR.length + 1);
- 36 if (name === "_TEMPLATE.md" || !existsSync(path)) continue;
- 37 if (!NAME.test(name)) problems.push(`${path}: name it YYYY-MM-DD-slug.md`);
- 38 else if (!REQUIRED.test(readFileSync(path, "utf8")))
- 39 problems.push(`${path}: needs a "**${LABEL}:**" line`);
- 40}
- 42// 2. The old single log: new "## " entries need the line too. Fenced blocks are skipped.
- 43function entries(text) {
- 44 const out = new Map();
- 45 let heading = null;
- 46 let inFence = false;
- 47 for (const line of text.split("\n")) {
- 48 if (line.startsWith("```")) inFence = !inFence;
- 49 if (!inFence && line.startsWith("## ")) {
- 50 heading = line.slice(3).trim();
- 51 out.set(heading, "");
- 52 continue;
- 53 }
- 54 if (heading !== null) out.set(heading, out.get(heading) + line + "\n");
- 55 }
- 56 for (const h of IGNORED) out.delete(h);
- 57 return out;
- 58}
- 59if (existsSync(LOG)) {
- 60 let before = "";
- 61 try {
- 62 before = execFileSync("git", ["show", `${base}:${LOG}`], { encoding: "utf8" });
- 63 } catch {
- 64 // The file is new on this branch: every entry in it is new.
- 65 }
- 66 const old = entries(before);
- 67 for (const [heading, body] of entries(readFileSync(LOG, "utf8")))
- 68 if (!old.has(heading) && !REQUIRED.test(body))
- 69 problems.push(`${LOG}: the new entry "## ${heading}" needs a "**${LABEL}:**" line`);
- 70}
- 72if (problems.length > 0) {
- 73 console.error("Session logs are missing something:");
- 74 for (const problem of problems) console.error(` - ${problem}`);
- 75 process.exit(1);
- 76}
- 77console.log("Every new session log is named right and has its required line.");
