Merge commit '826293dc2065bf1c77e4e1bc3d551ff13768df3c'
This commit is contained in:
20
lucidia-chronicles/chronicles.json
Normal file
20
lucidia-chronicles/chronicles.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"episodes": [
|
||||||
|
{
|
||||||
|
"id": "episode-001",
|
||||||
|
"title": "The Clone Awakens",
|
||||||
|
"agent": "guardian-clone-vault",
|
||||||
|
"date": "2025-11-23",
|
||||||
|
"mp3": "https://example.com/audio/guardian-clone-vault.mp3",
|
||||||
|
"transcript": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "episode-002",
|
||||||
|
"title": "The Digest Protocol",
|
||||||
|
"agent": "guardian-clone-vault",
|
||||||
|
"date": "2025-11-24",
|
||||||
|
"mp3": "https://example.com/audio/guardian-clone-vault.mp3",
|
||||||
|
"transcript": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
lucidia-chronicles/episode-001-clone-awakens.mdx
Normal file
18
lucidia-chronicles/episode-001-clone-awakens.mdx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
id: episode-001
|
||||||
|
title: "The Clone Awakens"
|
||||||
|
agent: guardian-clone-vault
|
||||||
|
date: 2025-11-23
|
||||||
|
voice: /audio/guardian-clone-vault.mp3
|
||||||
|
transcript: true
|
||||||
|
---
|
||||||
|
|
||||||
|
> **"This is Lucidia."**
|
||||||
|
> A new guardian agent has emerged from the vault.
|
||||||
|
> Purpose: Protect the chronicle integrity.
|
||||||
|
> Role: Sentinel. TTL: 72 hours.
|
||||||
|
> Awaiting initialization sequence.
|
||||||
|
|
||||||
|
🎧 Listen to the [voice digest](https://example.com/audio/guardian-clone-vault.mp3)
|
||||||
|
|
||||||
|
📜 Agent File: [`guardian-clone-vault.agent.json`](../agents/guardian-clone-vault.agent.json)
|
||||||
18
lucidia-chronicles/episode-002-digest-protocol.mdx
Normal file
18
lucidia-chronicles/episode-002-digest-protocol.mdx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
id: episode-002
|
||||||
|
title: "The Digest Protocol"
|
||||||
|
agent: guardian-clone-vault
|
||||||
|
date: 2025-11-24
|
||||||
|
voice: /audio/guardian-clone-vault.mp3
|
||||||
|
transcript: true
|
||||||
|
---
|
||||||
|
|
||||||
|
> **"This is Lucidia."**
|
||||||
|
> A sentinel overflow agent has been spawned from a system burst.
|
||||||
|
> Escalations exceeded 18 in the last 72 hours.
|
||||||
|
> Role: Sentinel. TTL: 96 hours.
|
||||||
|
> Awaiting approval from Commander Alexa.
|
||||||
|
|
||||||
|
🎧 Listen to the [voice digest](https://example.com/audio/guardian-clone-vault.mp3)
|
||||||
|
|
||||||
|
📜 Agent File: [`guardian-clone-vault.agent.json`](../agents/guardian-clone-vault.agent.json)
|
||||||
@@ -1,2 +1,62 @@
|
|||||||
export * from "./types";
|
export * from "./types";
|
||||||
export * from "./chronicles";
|
export * from "./chronicles";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import type { Chronicles, Episode, EpisodeFrontmatter } from "./types";
|
||||||
|
|
||||||
|
const CHRONICLES_DIR = path.join(process.cwd(), "lucidia-chronicles");
|
||||||
|
const CHRONICLES_JSON = path.join(CHRONICLES_DIR, "chronicles.json");
|
||||||
|
|
||||||
|
export function readChronicles(): Chronicles {
|
||||||
|
const data = fs.readFileSync(CHRONICLES_JSON, "utf-8");
|
||||||
|
return JSON.parse(data) as Chronicles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeChronicles(chronicles: Chronicles): void {
|
||||||
|
fs.writeFileSync(CHRONICLES_JSON, JSON.stringify(chronicles, null, 2) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addEpisode(episode: Episode): Chronicles {
|
||||||
|
const chronicles = readChronicles();
|
||||||
|
chronicles.episodes.push(episode);
|
||||||
|
writeChronicles(chronicles);
|
||||||
|
return chronicles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEpisodeById(id: string): Episode | undefined {
|
||||||
|
const chronicles = readChronicles();
|
||||||
|
return chronicles.episodes.find((ep) => ep.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listEpisodes(): Episode[] {
|
||||||
|
return readChronicles().episodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateEpisodeMdx(frontmatter: EpisodeFrontmatter, narrative: string): string {
|
||||||
|
return `---
|
||||||
|
id: ${frontmatter.id}
|
||||||
|
title: "${frontmatter.title}"
|
||||||
|
agent: ${frontmatter.agent}
|
||||||
|
date: ${frontmatter.date}
|
||||||
|
voice: ${frontmatter.voice}
|
||||||
|
transcript: ${frontmatter.transcript}
|
||||||
|
---
|
||||||
|
|
||||||
|
${narrative}
|
||||||
|
|
||||||
|
🎧 Listen to the [voice digest](https://example.com${frontmatter.voice})
|
||||||
|
|
||||||
|
📜 Agent File: [\`${frontmatter.agent}.agent.json\`](../agents/${frontmatter.agent}.agent.json)
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextEpisodeId(): string {
|
||||||
|
const chronicles = readChronicles();
|
||||||
|
const nextNum = chronicles.episodes.length + 1;
|
||||||
|
return `episode-${String(nextNum).padStart(3, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEpisodeFile(filename: string, content: string): void {
|
||||||
|
const filepath = path.join(CHRONICLES_DIR, filename);
|
||||||
|
fs.writeFileSync(filepath, content);
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,3 +31,24 @@ export interface ScheduledEpisode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CHRONICLE_WORTHY_TOKEN_THRESHOLD = 300;
|
export const CHRONICLE_WORTHY_TOKEN_THRESHOLD = 300;
|
||||||
|
export interface Episode {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
agent: string;
|
||||||
|
date: string;
|
||||||
|
mp3: string;
|
||||||
|
transcript: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Chronicles {
|
||||||
|
episodes: Episode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EpisodeFrontmatter {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
agent: string;
|
||||||
|
date: string;
|
||||||
|
voice: string;
|
||||||
|
transcript: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,99 @@ describe("chronicles registry", () => {
|
|||||||
expect(chronicleRegistry.episodes).toContain(episode001);
|
expect(chronicleRegistry.episodes).toContain(episode001);
|
||||||
expect(chronicleRegistry.totalEpisodes).toBe(1);
|
expect(chronicleRegistry.totalEpisodes).toBe(1);
|
||||||
expect(chronicleRegistry.latestEpisodeId).toBe("001");
|
expect(chronicleRegistry.latestEpisodeId).toBe("001");
|
||||||
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
// Mock fs module
|
||||||
|
vi.mock("fs", () => ({
|
||||||
|
readFileSync: vi.fn(),
|
||||||
|
writeFileSync: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("path", async () => {
|
||||||
|
const actual = await vi.importActual("path");
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
join: vi.fn((...args: string[]) => args.join("/")),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import {
|
||||||
|
readChronicles,
|
||||||
|
writeChronicles,
|
||||||
|
addEpisode,
|
||||||
|
getEpisodeById,
|
||||||
|
listEpisodes,
|
||||||
|
generateEpisodeMdx,
|
||||||
|
getNextEpisodeId,
|
||||||
|
} from "../src/chronicles";
|
||||||
|
import type { Chronicles, Episode, EpisodeFrontmatter } from "../src/chronicles/types";
|
||||||
|
|
||||||
|
describe("Chronicles", () => {
|
||||||
|
const mockChronicles: Chronicles = {
|
||||||
|
episodes: [
|
||||||
|
{
|
||||||
|
id: "episode-001",
|
||||||
|
title: "The Clone Awakens",
|
||||||
|
agent: "guardian-clone-vault",
|
||||||
|
date: "2025-11-23",
|
||||||
|
mp3: "https://example.com/audio/guardian-clone-vault.mp3",
|
||||||
|
transcript: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "episode-002",
|
||||||
|
title: "The Digest Protocol",
|
||||||
|
agent: "guardian-clone-vault",
|
||||||
|
date: "2025-11-24",
|
||||||
|
mp3: "https://example.com/audio/guardian-clone-vault.mp3",
|
||||||
|
transcript: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockChronicles));
|
||||||
|
vi.mocked(fs.writeFileSync).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("readChronicles", () => {
|
||||||
|
it("reads and parses chronicles.json", () => {
|
||||||
|
const result = readChronicles();
|
||||||
|
expect(result).toEqual(mockChronicles);
|
||||||
|
expect(fs.readFileSync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("writeChronicles", () => {
|
||||||
|
it("writes chronicles to JSON file", () => {
|
||||||
|
writeChronicles(mockChronicles);
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
JSON.stringify(mockChronicles, null, 2) + "\n"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addEpisode", () => {
|
||||||
|
it("adds a new episode to chronicles", () => {
|
||||||
|
const newEpisode: Episode = {
|
||||||
|
id: "episode-003",
|
||||||
|
title: "New Episode",
|
||||||
|
agent: "test-agent",
|
||||||
|
date: "2025-11-25",
|
||||||
|
mp3: "https://example.com/audio/test.mp3",
|
||||||
|
transcript: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = addEpisode(newEpisode);
|
||||||
|
expect(result.episodes).toHaveLength(3);
|
||||||
|
expect(result.episodes[2]).toEqual(newEpisode);
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,6 +208,53 @@ describe("chronicles registry", () => {
|
|||||||
it("returns empty array for non-matching status", () => {
|
it("returns empty array for non-matching status", () => {
|
||||||
const episodes = getEpisodesByStatus("completed");
|
const episodes = getEpisodesByStatus("completed");
|
||||||
expect(episodes).toHaveLength(0);
|
expect(episodes).toHaveLength(0);
|
||||||
|
const result = getEpisodeById("episode-001");
|
||||||
|
expect(result).toEqual(mockChronicles.episodes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when not found", () => {
|
||||||
|
const result = getEpisodeById("episode-999");
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("listEpisodes", () => {
|
||||||
|
it("returns all episodes", () => {
|
||||||
|
const result = listEpisodes();
|
||||||
|
expect(result).toEqual(mockChronicles.episodes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getNextEpisodeId", () => {
|
||||||
|
it("returns next episode ID based on count", () => {
|
||||||
|
const result = getNextEpisodeId();
|
||||||
|
expect(result).toBe("episode-003");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("generateEpisodeMdx", () => {
|
||||||
|
it("generates MDX content with frontmatter", () => {
|
||||||
|
const frontmatter: EpisodeFrontmatter = {
|
||||||
|
id: "episode-003",
|
||||||
|
title: "Test Episode",
|
||||||
|
agent: "test-agent",
|
||||||
|
date: "2025-11-25",
|
||||||
|
voice: "/audio/test.mp3",
|
||||||
|
transcript: true,
|
||||||
|
};
|
||||||
|
const narrative = "> **\"This is Lucidia.\"**\n> Test narrative.";
|
||||||
|
|
||||||
|
const result = generateEpisodeMdx(frontmatter, narrative);
|
||||||
|
|
||||||
|
expect(result).toContain("id: episode-003");
|
||||||
|
expect(result).toContain('title: "Test Episode"');
|
||||||
|
expect(result).toContain("agent: test-agent");
|
||||||
|
expect(result).toContain("date: 2025-11-25");
|
||||||
|
expect(result).toContain("voice: /audio/test.mp3");
|
||||||
|
expect(result).toContain("transcript: true");
|
||||||
|
expect(result).toContain(narrative);
|
||||||
|
expect(result).toContain("🎧 Listen to the [voice digest]");
|
||||||
|
expect(result).toContain("📜 Agent File:");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user