repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/the-system/bundle-slot.ts
Rawimport { randomBytes } from "node:crypto";
import { readFileSync } from "node:fs";
import path from "node:path";
import {
directoryForType,
discoverDocumentBundles,
indexBundleDocuments,
parseSystemConfig,
requireProjectRoot,
} from "./documents.js";
export type BundleType = "research" | "spec" | "phase" | "plan" | "issue";
export type BundleSlot = Readonly<{
type: BundleType;
title: string;
id: string;
slug: string;
path: string;
}>;
export function reserveBundleSlot(
cwd: string,
input: { type: BundleType; title: string },
reserved: Set<string> = new Set(),
): BundleSlot {
const root = requireProjectRoot(cwd);
const config = parseSystemConfig(
readFileSync(path.join(root, ".system/config.json"), "utf8"),
);
const discovery = discoverDocumentBundles(root);
const indexed = indexBundleDocuments(discovery.sources, config.prefix);
const existing = new Set([
...indexed.documents.map((document) => document.metadata.id),
...reserved,
]);
const title = input.title.trim();
if (!title) throw new Error("System bundle title must not be empty");
const id = generateDocumentId(config.prefix, input.type, existing);
reserved.add(id);
const slug = slugForTitle(title);
return {
type: input.type,
title,
id,
slug,
path: `.system/${directoryForType(input.type)}/${id}-${slug}/index.md`,
};
}
function generateDocumentId(
prefix: string,
type: BundleType,
existing: ReadonlySet<string>,
): string {
while (true) {
const token = randomBytes(6)
.toString("base64url")
.toUpperCase()
.replaceAll("-", "_");
const id = `${prefix}-${type.toUpperCase()}-${token}`;
if (!existing.has(id)) return id;
}
}
function slugForTitle(title: string): string {
const slug = title
.normalize("NFKD")
.replace(/\p{Mark}/gu, "")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 48)
.replace(/-$/g, "");
return slug || "bundle";
}