68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
|
|
import { sep } from "node:path";
|
||
|
|
|
||
|
|
/** POSIX-style path relative to repository root (e.g. `plans/Plan-a/Task-b.md`). */
|
||
|
|
export function toRepoPathPosix(relativeFromRepoRoot: string): string {
|
||
|
|
return relativeFromRepoRoot.split(sep).join("/");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Whether this repo-relative path should be imported as backlog markdown.
|
||
|
|
* Expects POSIX `plans/...` paths.
|
||
|
|
*/
|
||
|
|
export function isTrackedBacklogMarkdown(repoPathPosix: string): boolean {
|
||
|
|
if (!repoPathPosix.startsWith("plans/")) return false;
|
||
|
|
if (repoPathPosix === "plans/README.md") return false;
|
||
|
|
const base = repoPathPosix.split("/").pop() ?? "";
|
||
|
|
return (
|
||
|
|
base.startsWith("Plan-") || base.startsWith("Epic-") || base.startsWith("Task-")
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parent document path for hierarchy links, or `null` for plan roots.
|
||
|
|
*/
|
||
|
|
export function parentRepoPath(repoPathPosix: string): string | null {
|
||
|
|
const parts = repoPathPosix.split("/").filter(Boolean);
|
||
|
|
if (parts.length < 3 || parts[0] !== "plans") return null;
|
||
|
|
|
||
|
|
const planDir = parts[1];
|
||
|
|
if (!planDir.startsWith("Plan-")) return null;
|
||
|
|
|
||
|
|
if (parts.length === 3) {
|
||
|
|
const file = parts[2];
|
||
|
|
if (file.startsWith("Plan-") && file.endsWith(".md")) return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (parts.length === 4) {
|
||
|
|
const file = parts[3];
|
||
|
|
if (file.startsWith("Epic-") && file.endsWith(".md")) {
|
||
|
|
return `plans/${planDir}/${planDir}.md`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (parts.length === 5) {
|
||
|
|
const epicDir = parts[2];
|
||
|
|
const file = parts[4];
|
||
|
|
if (epicDir.startsWith("Epic-") && file.startsWith("Task-") && file.endsWith(".md")) {
|
||
|
|
return `plans/${planDir}/${epicDir}/${epicDir}.md`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function planSlugFromPath(repoPathPosix: string): string | null {
|
||
|
|
const parts = repoPathPosix.split("/").filter(Boolean);
|
||
|
|
if (parts.length < 2 || parts[0] !== "plans") return null;
|
||
|
|
const planDir = parts[1];
|
||
|
|
return planDir.startsWith("Plan-") ? planDir.replace(/^Plan-/, "") : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function epicFolderFromPath(repoPathPosix: string): string | null {
|
||
|
|
const parts = repoPathPosix.split("/").filter(Boolean);
|
||
|
|
if (parts.length < 3 || parts[0] !== "plans") return null;
|
||
|
|
const epicDir = parts[2];
|
||
|
|
if (parts.length >= 4 && epicDir.startsWith("Epic-")) return epicDir.replace(/^Epic-/, "");
|
||
|
|
return null;
|
||
|
|
}
|