dashboard: tasks move to <project>/todo.txt at root, auto-gitignore in git repos

This commit is contained in:
2026-08-04 12:08:35 +10:00
parent ef22ec6a6e
commit 0d153a72b0

View File

@@ -51,15 +51,46 @@ const AUTO_ZELLIJ_NAME = /^[a-z]+-[a-z]+$/;
// pi-subagents names subagent sessions "<Base>#<8-hex>" (e.g. "Explore#fb073d02") // pi-subagents names subagent sessions "<Base>#<8-hex>" (e.g. "Explore#fb073d02")
const SUBAGENT_NAME = /^.+?#[0-9a-f]{8}$/i; const SUBAGENT_NAME = /^.+?#[0-9a-f]{8}$/i;
// Project-local task file: lives in the pi project's .pi/ directory // Project task file: lives at the root of the pi project's working directory
// Mirrors how pi sessions are organized by working directory // so it is visible and discoverable. In git repos it is automatically added to
function projectTasksDir(): string { // .gitignore so task churn never pollutes commits or Hunk reviews.
const dir = join(process.cwd(), ".pi", "dashboard", "tasks"); function gitRoot(dir: string): string | null {
ensureDir(dir); let cur = dir;
return dir; for (;;) {
if (existsSync(join(cur, ".git"))) return cur;
const parent = join(cur, "..");
if (parent === cur) return null;
cur = parent;
} }
}
function ensureTodoGitignored() {
const root = gitRoot(cwd());
if (!root) return; // not a git repo — nothing to ignore
const gi = join(root, ".gitignore");
let content = "";
if (existsSync(gi)) {
try {
content = readFileSync(gi, "utf-8");
} catch {
return;
}
}
const lines = content.split("\n");
if (lines.some((l) => l.trim() === "todo.txt" || l.trim() === "/todo.txt")) {
return;
}
const line = content === "" || content.endsWith("\n") ? "todo.txt\n" : "\ntodo.txt\n";
try {
writeFileSync(gi, content + line, "utf-8");
} catch {
// read-only repo — ignore; tasks still work, they just may show in git
}
}
function projectTodotxt(): string { function projectTodotxt(): string {
return join(projectTasksDir(), "todo.txt"); ensureTodoGitignored();
return join(cwd(), "todo.txt");
} }
// ── Types ───────────────────────────────────────────────────────── // ── Types ─────────────────────────────────────────────────────────
@@ -168,7 +199,6 @@ function appendToTodo(
priority?: string, priority?: string,
agent?: string agent?: string
) { ) {
ensureDir(projectTasksDir());
const pri = priority ? `(${priority.toUpperCase()}) ` : ""; const pri = priority ? `(${priority.toUpperCase()}) ` : "";
const proj = project ? ` +project:${project}` : ""; const proj = project ? ` +project:${project}` : "";
const agentTag = agent ? ` +agent:${agent}` : ""; const agentTag = agent ? ` +agent:${agent}` : "";
@@ -208,7 +238,6 @@ function readTodoList() {
export default function (pi: ExtensionAPI) { export default function (pi: ExtensionAPI) {
ensureDir(DASHBOARD_DIR); ensureDir(DASHBOARD_DIR);
ensureDir(projectTasksDir());
const machine = hostname(); const machine = hostname();
const inTmux = !!process.env.TMUX; const inTmux = !!process.env.TMUX;