diff --git a/apps/server/tests/unit/lib/app-spec-format.test.ts b/apps/server/tests/unit/lib/app-spec-format.test.ts
index a2fdd11e..43eb5359 100644
--- a/apps/server/tests/unit/lib/app-spec-format.test.ts
+++ b/apps/server/tests/unit/lib/app-spec-format.test.ts
@@ -1,57 +1,189 @@
import { describe, it, expect } from "vitest";
import {
- APP_SPEC_XML_FORMAT,
+ specToXml,
+ getStructuredSpecPromptInstruction,
getAppSpecFormatInstruction,
+ APP_SPEC_XML_FORMAT,
+ type SpecOutput,
} from "@/lib/app-spec-format.js";
describe("app-spec-format.ts", () => {
- describe("APP_SPEC_XML_FORMAT", () => {
- it("should export a non-empty string constant", () => {
- expect(typeof APP_SPEC_XML_FORMAT).toBe("string");
- expect(APP_SPEC_XML_FORMAT.length).toBeGreaterThan(0);
+ describe("specToXml", () => {
+ it("should convert minimal spec to XML", () => {
+ const spec: SpecOutput = {
+ project_name: "Test Project",
+ overview: "A test project",
+ technology_stack: ["TypeScript", "Node.js"],
+ core_capabilities: ["Testing", "Development"],
+ implemented_features: [
+ { name: "Feature 1", description: "First feature" },
+ ],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).toContain('');
+ expect(xml).toContain("");
+ expect(xml).toContain("");
+ expect(xml).toContain("Test Project");
+ expect(xml).toContain("TypeScript");
+ expect(xml).toContain("Testing");
});
- it("should contain XML format documentation", () => {
- expect(APP_SPEC_XML_FORMAT).toContain("");
- expect(APP_SPEC_XML_FORMAT).toContain("");
- expect(APP_SPEC_XML_FORMAT).toContain("");
- expect(APP_SPEC_XML_FORMAT).toContain("");
- expect(APP_SPEC_XML_FORMAT).toContain("");
- expect(APP_SPEC_XML_FORMAT).toContain("");
+ it("should escape XML special characters", () => {
+ const spec: SpecOutput = {
+ project_name: "Test & Project",
+ overview: "Description with ",
+ technology_stack: ["TypeScript"],
+ core_capabilities: ["Cap"],
+ implemented_features: [],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).toContain("Test & Project");
+ expect(xml).toContain("<tags>");
});
- it("should contain XML escaping instructions", () => {
- expect(APP_SPEC_XML_FORMAT).toContain("<");
- expect(APP_SPEC_XML_FORMAT).toContain(">");
- expect(APP_SPEC_XML_FORMAT).toContain("&");
+ it("should include file_locations when provided", () => {
+ const spec: SpecOutput = {
+ project_name: "Test",
+ overview: "Test",
+ technology_stack: ["TS"],
+ core_capabilities: ["Cap"],
+ implemented_features: [
+ {
+ name: "Feature",
+ description: "Desc",
+ file_locations: ["src/index.ts"],
+ },
+ ],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).toContain("");
+ expect(xml).toContain("src/index.ts");
+ });
+
+ it("should not include file_locations when empty", () => {
+ const spec: SpecOutput = {
+ project_name: "Test",
+ overview: "Test",
+ technology_stack: ["TS"],
+ core_capabilities: ["Cap"],
+ implemented_features: [
+ { name: "Feature", description: "Desc", file_locations: [] },
+ ],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).not.toContain("");
+ });
+
+ it("should include additional_requirements when provided", () => {
+ const spec: SpecOutput = {
+ project_name: "Test",
+ overview: "Test",
+ technology_stack: ["TS"],
+ core_capabilities: ["Cap"],
+ implemented_features: [],
+ additional_requirements: ["Node.js 18+"],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).toContain("");
+ expect(xml).toContain("Node.js 18+");
+ });
+
+ it("should include development_guidelines when provided", () => {
+ const spec: SpecOutput = {
+ project_name: "Test",
+ overview: "Test",
+ technology_stack: ["TS"],
+ core_capabilities: ["Cap"],
+ implemented_features: [],
+ development_guidelines: ["Use ESLint"],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).toContain("");
+ expect(xml).toContain("Use ESLint");
+ });
+
+ it("should include implementation_roadmap when provided", () => {
+ const spec: SpecOutput = {
+ project_name: "Test",
+ overview: "Test",
+ technology_stack: ["TS"],
+ core_capabilities: ["Cap"],
+ implemented_features: [],
+ implementation_roadmap: [
+ { phase: "Phase 1", status: "completed", description: "Setup" },
+ ],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).toContain("");
+ expect(xml).toContain("completed");
+ });
+
+ it("should not include optional sections when empty", () => {
+ const spec: SpecOutput = {
+ project_name: "Test",
+ overview: "Test",
+ technology_stack: ["TS"],
+ core_capabilities: ["Cap"],
+ implemented_features: [],
+ additional_requirements: [],
+ development_guidelines: [],
+ implementation_roadmap: [],
+ };
+
+ const xml = specToXml(spec);
+
+ expect(xml).not.toContain("");
+ expect(xml).not.toContain("");
+ expect(xml).not.toContain("");
+ });
+ });
+
+ describe("getStructuredSpecPromptInstruction", () => {
+ it("should return non-empty prompt instruction", () => {
+ const instruction = getStructuredSpecPromptInstruction();
+ expect(instruction).toBeTruthy();
+ expect(instruction.length).toBeGreaterThan(100);
+ });
+
+ it("should mention required fields", () => {
+ const instruction = getStructuredSpecPromptInstruction();
+ expect(instruction).toContain("project_name");
+ expect(instruction).toContain("overview");
+ expect(instruction).toContain("technology_stack");
});
});
describe("getAppSpecFormatInstruction", () => {
- it("should return a string containing the XML format", () => {
+ it("should return non-empty format instruction", () => {
const instruction = getAppSpecFormatInstruction();
- expect(typeof instruction).toBe("string");
- expect(instruction).toContain(APP_SPEC_XML_FORMAT);
+ expect(instruction).toBeTruthy();
+ expect(instruction.length).toBeGreaterThan(100);
});
- it("should contain critical formatting requirements", () => {
+ it("should include critical formatting requirements", () => {
const instruction = getAppSpecFormatInstruction();
expect(instruction).toContain("CRITICAL FORMATTING REQUIREMENTS");
- expect(instruction).toContain("");
- expect(instruction).toContain("");
});
+ });
- it("should contain verification instructions", () => {
- const instruction = getAppSpecFormatInstruction();
- expect(instruction).toContain("VERIFICATION");
- expect(instruction).toContain("exactly one root XML element");
- });
-
- it("should instruct not to use markdown", () => {
- const instruction = getAppSpecFormatInstruction();
- expect(instruction).toContain("Do NOT use markdown");
- expect(instruction).toContain("no # headers");
- expect(instruction).toContain("no **bold**");
+ describe("APP_SPEC_XML_FORMAT", () => {
+ it("should contain valid XML template structure", () => {
+ expect(APP_SPEC_XML_FORMAT).toContain("");
+ expect(APP_SPEC_XML_FORMAT).toContain("");
});
});
});
diff --git a/apps/server/tests/unit/lib/automaker-paths.test.ts b/apps/server/tests/unit/lib/automaker-paths.test.ts
index e8720663..10797eb8 100644
--- a/apps/server/tests/unit/lib/automaker-paths.test.ts
+++ b/apps/server/tests/unit/lib/automaker-paths.test.ts
@@ -16,16 +16,19 @@ import {
} from "@/lib/automaker-paths.js";
describe("automaker-paths.ts", () => {
- const projectPath = "/test/project";
+ const projectPath = path.join("/test", "project");
describe("getAutomakerDir", () => {
it("should return path to .automaker directory", () => {
- expect(getAutomakerDir(projectPath)).toBe("/test/project/.automaker");
+ expect(getAutomakerDir(projectPath)).toBe(
+ path.join(projectPath, ".automaker")
+ );
});
it("should handle paths with trailing slashes", () => {
- expect(getAutomakerDir("/test/project/")).toBe(
- path.join("/test/project/", ".automaker")
+ const pathWithSlash = path.join("/test", "project") + path.sep;
+ expect(getAutomakerDir(pathWithSlash)).toBe(
+ path.join(pathWithSlash, ".automaker")
);
});
});
@@ -33,7 +36,7 @@ describe("automaker-paths.ts", () => {
describe("getFeaturesDir", () => {
it("should return path to features directory", () => {
expect(getFeaturesDir(projectPath)).toBe(
- "/test/project/.automaker/features"
+ path.join(projectPath, ".automaker", "features")
);
});
});
@@ -41,13 +44,13 @@ describe("automaker-paths.ts", () => {
describe("getFeatureDir", () => {
it("should return path to specific feature directory", () => {
expect(getFeatureDir(projectPath, "feature-123")).toBe(
- "/test/project/.automaker/features/feature-123"
+ path.join(projectPath, ".automaker", "features", "feature-123")
);
});
it("should handle feature IDs with special characters", () => {
expect(getFeatureDir(projectPath, "my-feature_v2")).toBe(
- "/test/project/.automaker/features/my-feature_v2"
+ path.join(projectPath, ".automaker", "features", "my-feature_v2")
);
});
});
@@ -55,27 +58,31 @@ describe("automaker-paths.ts", () => {
describe("getFeatureImagesDir", () => {
it("should return path to feature images directory", () => {
expect(getFeatureImagesDir(projectPath, "feature-123")).toBe(
- "/test/project/.automaker/features/feature-123/images"
+ path.join(projectPath, ".automaker", "features", "feature-123", "images")
);
});
});
describe("getBoardDir", () => {
it("should return path to board directory", () => {
- expect(getBoardDir(projectPath)).toBe("/test/project/.automaker/board");
+ expect(getBoardDir(projectPath)).toBe(
+ path.join(projectPath, ".automaker", "board")
+ );
});
});
describe("getImagesDir", () => {
it("should return path to images directory", () => {
- expect(getImagesDir(projectPath)).toBe("/test/project/.automaker/images");
+ expect(getImagesDir(projectPath)).toBe(
+ path.join(projectPath, ".automaker", "images")
+ );
});
});
describe("getWorktreesDir", () => {
it("should return path to worktrees directory", () => {
expect(getWorktreesDir(projectPath)).toBe(
- "/test/project/.automaker/worktrees"
+ path.join(projectPath, ".automaker", "worktrees")
);
});
});
@@ -83,7 +90,7 @@ describe("automaker-paths.ts", () => {
describe("getAppSpecPath", () => {
it("should return path to app_spec.txt file", () => {
expect(getAppSpecPath(projectPath)).toBe(
- "/test/project/.automaker/app_spec.txt"
+ path.join(projectPath, ".automaker", "app_spec.txt")
);
});
});
@@ -91,7 +98,7 @@ describe("automaker-paths.ts", () => {
describe("getBranchTrackingPath", () => {
it("should return path to active-branches.json file", () => {
expect(getBranchTrackingPath(projectPath)).toBe(
- "/test/project/.automaker/active-branches.json"
+ path.join(projectPath, ".automaker", "active-branches.json")
);
});
});