Bundles in-flight ECHODO work with the Coolify deployment configuration: App - New routes: ai, forms, planner, settings (templates/types), teams, doc detail, whiteboard detail - New components: app shell rework (icon-rail, top-header), forms builder/renderer/responses, types manager, objects creation dialog, card primitive, form + overview views - New tRPC routers: favorites, forms, types, workspaces; updates to health and objects routers - Markdown backlog sync (packages/database) + cursor-sync schema/migrations - Schema additions: forms, types, favorites, markdown_backlog, cursor_sync - Initial Drizzle migrations checked in Deployment - docker/docker-compose.coolify.yml: drops bundled Postgres/Redis (uses CT 102 shared services), removes host port mappings, adds Coolify SERVICE_FQDN_* magic vars for web + collab - .env.example rewritten as the full ECHODO/Coolify variable manifest - NextAuth gains an Authentik OIDC provider (gated on env presence) - Root layout injects Umami tracking script when configured; metadata title flipped to ECHODO Security - .gitignore expanded to exclude AGENT-DEPLOY.md, .env.*, secrets/, credentials.*, *.key, *.crt, *.pem, ssh keys Made-with: Cursor
67 lines
2.2 KiB
TypeScript
67 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;
|
|
}
|